Introduction

In this class I will

Materials for this class are available on GitHub at https://github.com/ltierney/SIBS-WV-2021.git.

Materials for our Data Visualization and Data Technologies course are available at http://www.stat.uiowa.edu/~luke/classes/STAT4580-2021/

Some tools I will be using:

Most of the packages are loaded by loading the tidyverse package:

library(tidyverse)

Useful references:

Hadley Wickham and Garrett Grolemund (2016), R for Data Science, O'Reilly.

Claus O. Wilke (2019), Fundamentals of Data Visualization, O'Reilly.

Kieran Healy (2018) Data Visualization: A practical introduction, Princeton

Rafael A. Irizarry (2019), Introduction to Data Science: Data Analysis and Prediction Algorithms with R, Chapman & Hall/CRC. (Book source on GitHub)

Ask questions any time!

The R Language

R is a language for data analysis and graphics.

  • R was originally developed by Robert Gentleman and Ross Ihaka in the early 1990's for a Macintosh computer lab at U. of Auckland, New Zealand.

  • R is based on the S language developed by John Chambers and others at Bell Labs.

R is an Open Source project.

  • Since 1997 R is developed and maintained by the R-core group, with around 20 members located in maor than 10 different countries.

  • R is widely used in the field of statistics and beyond, especially in university environments.

  • R has become the primary framework for developing and making available new statistical methodology.

  • Many (now over 17,000) extension packages are available through CRAN or similar repositories.

Working with R

R is designed for interactive data exploration.

  • Interaction is through a read-eval-print loop (REPL).
  • This is also called a command line interface (CLI).

All computations are specified in the R language.

  • Even for simple tasks you need to know a little of the language.
  • After learning to do simple tasks you know some of the language.

The language is used to

  • prepare data for analysis;
  • specify individual analyses;
  • program repeated or similar analyses;
  • program new methods of analysis.

Specifying these tasks in a language supports reproducible research.

The R language operates on vectors and arrays.

Commonly used data types are:

  • integer and numeric vectors;
  • logical vectors;
  • character vectors;
  • factors.

All basic vector types support missing (NA) values.

Arithmetic operations are vectorized to operate element-wise on vectors.

Data vectors are usually combined into table-like objects called data frames.

The Data Analysis Process

A figure that shows the steps usually involved in a data analysis project:

These steps are often repeated many times, so it is important to make your work reproducible.

Reproducible Data Analysis

Making your work reproducible:

  • Save you work in a text file or notebook.

  • Track changes to your files with a version control system like git.

  • Use a system like Rmarkdown to prepare your reports.

This allows you to re-create your report when data changes (as it often will!).

A good resource for setting up your tools to support this is Happy Git and GitHub for the useR.

Some Examples

Working with research data a first step is usually to read and clean the data.

We'll put that off for a little while and work with some data sets made available in R packages.

Data sets available in R packages include:

Old Faithful Eruptions

A simple classic data set is the geyser data frame available in package MASS

data(geyser, package = "MASS")
dim(geyser)
## [1] 299   2
head(geyser)
##   waiting duration
## 1      80 4.016667
## 2      71 2.150000
## 3      57 4.000000
## 4      80 4.000000
## 5      75 4.000000
## 6      77 2.000000

head and tail return the first and last few rows of a data frame.

They are useful for quick sanity checks.

The rows represent measurements recorded for eruptions of the Old Faithful geyser in Yellowstone National Park, Wyoming.

The variables are:

  • waiting: the time in minutes since the previous eruption;
  • duration: the duration of the eruption.

The durations have a bimodal distribution:

ggplot(geyser) +
    geom_histogram(aes(x = duration), bins = 15, color = "black", fill = "grey")

A basic template for creating a plot with ggplot:

ggplot(data = <DATA>) + <GEOM>(mapping = aes(<MAPPINGS>))

An interesting question is whether the duration can be used to predict when the next eruption will occur.

A plot of the previous duration against the waiting time to the current eruption:

ggplot(geyser) + geom_point(aes(x = lag(duration), y = waiting))
## Warning: Removed 1 rows containing missing values (geom_point).

It looks like a useful rule would be to expect a shorter waiting time after a shorter eruption.

An interesting feature: Many durations are recorded as 2 or 4 minutes. This can also be seen in a histogram with many small bins:

p <- ggplot(geyser) +
    geom_histogram(aes(x = duration, y = stat(density)),
                   fill = "grey", color = "black", bins = 50)
p

ggplot produces a plot object. Drawing only happens when the object is printed.

Does this rounding matter?

  • For many analyses it probably doesn't.
  • It might if you wanted to fit normal distributions to the two groups.

Taking 3 minutes as the divide between short and long durations we can compute the means and standard deviations as

d <- geyser$duration
d_short <- d[d < 3]
d_long <- d[d >= 3]
mean(d_short)
## [1] 1.980317
sd(d_short)
## [1] 0.2779829
mean(d_long)
## [1] 4.262113
sd(d_long)
## [1] 0.3937525
mean(d >= 3)
## [1] 0.6488294

An approach that scales better is to compute group summaries using tools from the dplyr tidyverse package.

First, add a type variable:

geyser <- mutate(geyser, type = ifelse(duration < 3, "short", "long"))

The summaries can then be computed as

sgd <- summarize(group_by(geyser, type),
                 mean = mean(duration),
                 sd = sd(duration),
                 n = n())
(sgd <- mutate(sgd, prop = n / sum(n)))
## # A tibble: 2 x 5
##   type   mean    sd     n  prop
##   <chr> <dbl> <dbl> <int> <dbl>
## 1 long   4.26 0.394   194 0.649
## 2 short  1.98 0.278   105 0.351

This computation can also be written using the forward pipe operator %>%:

sgd <-
    group_by(geyser, type) %>%
    summarize(mean = mean(duration),
              sd = sd(duration),
              n = n()) %>%
    ungroup() %>%
    mutate(prop = n / sum(n))

The pipe operator allows a sequence of operations to be chained together.

The left-hand operation is passed implicitly as the first argument to the function called on the right.

One way to show the superimposed normal densities:

f1 <- function(x)
    sgd$prop[1] * dnorm(x, sgd$mean[1], sgd$sd[1])
f2 <- function(x)
    sgd$prop[2] * dnorm(x, sgd$mean[2], sgd$sd[2])
p <- p +
    stat_function(color = "red", fun = f1) +
    stat_function(color = "blue", fun = f2)
p

A ggplot can consist of several layers.

The means and standard deviations are affected by the rounding. Summaries that omit values equal to 2 or 4 minutes can be computed as

geyser2 <- filter(geyser, duration != 2, duration != 4)
sgd2 <-
    group_by(geyser2, type) %>%
    summarize(mean = mean(duration),
              sd = sd(duration),
              n = n()) %>%
    ungroup() %>%
    mutate(prop = n / sum(n))
sgd2
## # A tibble: 2 x 5
##   type   mean    sd     n  prop
##   <chr> <dbl> <dbl> <int> <dbl>
## 1 long   4.36 0.422   141 0.632
## 2 short  1.97 0.315    82 0.368
summarize, group_by, and mutate are from the dplyr package that implements a grammar of data manipulation.

A plot showing curves computed both ways:

f1_2 <- function(x)
    sgd2$prop[1] * dnorm(x, sgd2$mean[1], sgd2$sd[1])
f2_2 <- function(x)
    sgd2$prop[2] * dnorm(x, sgd2$mean[2], sgd2$sd[2])
p <- p +
    stat_function(color = "red",
                  linetype = 2,
                  fun = f1_2) +
    stat_function(color = "blue",
                  linetype = 2,
                  fun = f2_2)
p

Minnesota Barley Yields

A classic data set: Total yield in bushels per acre for 10 varieties at 6 sites in Minnesota in each of two years, 1931 and 1932.

The raw data:

data(barley, package = "lattice")
head(barley)
##      yield   variety year            site
## 1 27.00000 Manchuria 1931 University Farm
## 2 48.86667 Manchuria 1931          Waseca
## 3 27.43334 Manchuria 1931          Morris
## 4 39.93333 Manchuria 1931       Crookston
## 5 32.96667 Manchuria 1931    Grand Rapids
## 6 28.96667 Manchuria 1931          Duluth

Some initial plots:

p1 <- ggplot(barley) + geom_point(aes(x = yield, y = variety))
p2 <- ggplot(barley) + geom_point(aes(x = yield, y = site))
cowplot::plot_grid(p1, p2)

Using color to separate yields in the two years:

p1 <- ggplot(barley) + geom_point(aes(x = yield, y = variety, color = year))
p2 <- ggplot(barley) + geom_point(aes(x = yield, y = site, color = year))
cowplot::plot_grid(p1, p2)

Can we also show site using symbol shape?

ggplot(barley) +
    geom_point(aes(x = yield, y = variety, color = year, shape = site))

There is a lot of interference between shape and color.

Possible improvements:

  • jittering;
  • larger points.
ggplot(barley) +
    geom_point(aes(x = yield, y = variety, color = year, shape = site),
               position = position_jitter(height = 0.15, width = 0),
               size = 2)

Another approach: faceting to produce small multiples.

ggplot(barley) +
    geom_point(aes(x = yield, y = variety, color = year)) +
    facet_wrap(~site)

Focusing on summaries can help. Bar charts are sometimes used for summaries, but dot plots are usually a better choice.

barley_site_year <-
    group_by(barley, site, year) %>%
    summarize(yield = mean(yield)) %>%
    ungroup()
## `summarise()` has grouped output by 'site'. You can override using the `.groups` argument.
p1 <- ggplot(barley_site_year) +
    geom_point(aes(y = site, x = yield, color = year), size = 3)
p2 <- ggplot(barley_site_year) +
    geom_col(aes(x = site, y = yield, fill = year),
             size = 3,
             position = "dodge", width = .4) +
    coord_flip()
cowplot::plot_grid(p1, p2)

Because of the way we perceive bars, it is important to use a zero base line for bar charts.

Hair and Eye Color Data

A data set recording the distribution of hair and eye color and sex in 592 statistics students.

The data set is available as a cross-tabulation; as.data.frame converts it to a data frame.

HairEyeDF <- as.data.frame(HairEyeColor)
head(HairEyeDF)
##    Hair   Eye  Sex Freq
## 1 Black Brown Male   32
## 2 Brown Brown Male   53
## 3   Red Brown Male   10
## 4 Blond Brown Male    3
## 5 Black  Blue Male   11
## 6 Brown  Blue Male   50

Looking at the distribution of eye color:

eye <-
    group_by(HairEyeDF, Eye) %>%
    summarize(Freq = sum(Freq)) %>%
    ungroup()
ggplot(eye) +
    geom_col(aes(x = Eye, y = Freq), position = "dodge")

Mapping eye color to color in addition to the horizontal axis can help:

ggplot(eye) + geom_col(aes(x = Eye, y = Freq, fill = Eye), position = "dodge")

More sensible colors would be nice but requires a bit of work:

hazel_rgb <- col2rgb("brown") * 0.75 + col2rgb("green") * 0.25
hazel <- do.call(rgb, as.list(hazel_rgb / 255))

cols <- c(Blue = colorspace::lighten(colorspace::desaturate("blue", 0.3), 0.3),
          Green = colorspace::lighten("forestgreen", 0.1),
          Brown = colorspace::lighten("brown", 0.0001), ## 0.3?
          Hazel = colorspace::lighten(hazel, 0.3))

pb <- ggplot(eye) +
    geom_col(aes(x = Eye, y = Freq, fill = Eye), position = "dodge") +
    scale_fill_manual(values = cols)
pb

A stacked bar chart can also be useful:

psb <- ggplot(eye) +
    geom_col(aes(x = "", y = Freq, fill = Eye), color = "lightgrey") +
    scale_fill_manual(values = cols)
psb

A pie chart can be seen as a stacked bar chart in polar coordinates:

(pp <- psb + coord_polar("y"))

The axis and grid are not helpful; a theme adjustment can remove them:

(pp <- pp + theme_void())

Themes are a way to customize the non-data components of plots: i.e. titles, labels, fonts, background, gridlines, and legends. Themes can be used to give plots a consistent customized look.

The ggthemes package provides a number of themes to emulate the style of different publications, for example theme_wsj and theme_economist.

How well do bar charts and pie charts work?

cowplot::plot_grid(pb, pp)

Some questions:

  • Which plot makes it easier to tell whether the proportion of brown-eyed students is larger or smaller that the proportion of blue-eyed students.

  • Which plot makes it easier to tell whether these proportions are larger or smaller than 1/2 or 1/4 or 1/3?

Looking at the proportions within hair color and sex:

eye_hairsex <-
    group_by(HairEyeDF, Hair, Sex) %>%
    mutate(Prop = Freq / sum(Freq)) %>%
    ungroup()
p1 <- ggplot(eye_hairsex) +
    geom_col(aes(x = Eye, y = Prop, fill = Eye)) +
    scale_fill_manual(values = cols) +
    facet_grid(Hair~Sex)
p2 <- ggplot(eye_hairsex) +
    geom_col(aes(x = "", y = Prop, fill = Eye)) +
    scale_fill_manual(values = cols) +
    coord_polar("y")+facet_grid(Hair~Sex) +
    theme_void()
cowplot::plot_grid(p1, p2)

A more complete ggplot template:

ggplot(data = <DATA>) +
    <GEOM>(mapping = aes(<MAPPINGS>),
           stat = <STAT>,
           position = <POSITION>) +
    < ... MORE GEOMS ... > +
    <COORDINATE_ADJUSTMENT> +
    <SCALE_ADJUSTMENT> +
    <FACETING> +
    <THEME_ADJUSTMENT>

Perception and the Grammar of Graphics

river <- scan("data/river.dat")
rd <- data.frame(flow = river, month = seq_along(river))
(pp <- ggplot(rd) + geom_point(aes(x = month, y = flow)))

(pl <- ggplot(rd) + geom_line(aes(x = month, y = flow)))
pp + coord_fixed(3.5)
pl + coord_fixed(3.5)

A Simple Model of Visual Perception

The eyes acquire an image, which is processed through three stages of memory:

  • Iconic memory
  • Working memory, or short-term memory
  • Long-term memory

The first processing stage of an image happens in iconic memory.

  • Images remain in iconic memory for less than a second.
  • Processing in iconic memory is massively parallel and automatic.
  • This is called preattentive processing.

Preattentive processing is a fast recognition process.

Meaningful visual chunks are moved from iconic memory to short term memory.

  • These chunks are used by conscious, or attentive, processing.
  • Attentive processing often involves conscious comparisons or search.
  • Short term memory is limited;
    • information is retained for only a few seconds;
    • only three or fours chunks can be held at a time.

Long term visual memory is built up over a lifetime, though infrequently used visual chunks may become lost.

Visual Design Implications

  • Try to make as much use of preattentive features as possible.

  • Recognize when preattentive features might mislead.

  • For features that require attentive processing keep in mind that working memory is limited.

Some Terms for Describing Visualizations

  • Data to be visualized contains variables or attributes measured on individual items or cases.

  • Links are relationships that may exist among items, e.g. months within a year or countries within a continent.

  • Marks are individual geometric entities used to represent items: points. bars, etc.

  • Aesthetics or visual channels are the visual features of marks that can be used to encode attributes.

The aes(...) expressions establish the mapping between attributes and visual channels.

These ideas closely mirror the structure of the grammar of graphics as implemented in ggplot.

Munzner, T. (2014), Visualization Analysis and Design, CRC Press.

Wilkinson, L. (2005), The Grammar of Graphics, 2nd ed, Springer.

Channels and their Accuracy

A useful distinction among channels:

  • Magnitude channels can reflect order and numeric values, e.g. position on an axis, length, area, brightness.

  • Identity channels can distinguish different values but not reflect order, e.g. hue, shape, grouping.

Some channels are better at conveying information than others.

Munzner's ordering by accuracy:

Magnitude Channels (Ordered, Numerical) Identity Channels (Categorical)
Position on common scale Spatial grouping
Position on unaligned scale Color hue
Length (1D size) Shape
Tilt, angle
Area (2D size)
Depth (3D position)
Color luminance, saturation
Curvature, volume (3D size)

Line width is another channel; not sure there is agreement on its accuracy, but it is not high.

Visual Design Implications

Try to map the most important variables to the strongest channels.

Color

Color is very effective when used well.

But using color well is not easy.

Some of the issues:

  • Perception depends on context.

  • Simple color assignments may not separate equally well.

  • Effectiveness may vary with the medium (screen, projector, print).

  • Some people do not perceive the full specturm of colors.

  • Grey scale printing.

  • Some colors have cultural significance.

  • Cultural significance may vary among cultures and with time.

Color perception is relative:

A note on rainbow colors.

Some tools for selecting palettes include:

  • ColorBrewer; available in the RColorBrewer package.

  • HCL Wizard; also available as hclwizard in the colorspace package.

A Grammar of Data Manipulation

The dplyr package provides a language, or grammar, for data manipulation.

The language contains a number of verbs that operate on tables.

The most commonly used verbs operate on a single data frame:

There are also a number of join verbs that merge several data frames into one.

The tidyr package provides additional verbs, such as pivot_longer and pivot_wider for reshaping data frames.

The single table verbs can also be used with group_by to work a group at a time instead of applying to the entire data frame.

The design of dplyr is strongly motivated by SQL.

More Examples

These examples start with raw data as you might receive it from a researcher, and involve reading and cleaning the data.

Wind Turbines in Iowa

There are many wind turbines in Iowa. Data is available from the U.S. Wind Turbine Database. A snapshot is available is here as a CSV file.

CSV files are a common form of data exchange.

  • They are simple text files that are intended to be written and read by a computer.

  • Some CSV files include a header and a footer that need to he handled.

  • One issue is that a comma isn't a good separator in countries where it is the decimal separator!

  • A CSV file can be read using read.csv or readr::read_csv.

Reading the wind turbine data:

wind_turbines <- read.csv("data/us_wind.csv", comment = "#")

Some data cleaning is needed.

Focus on the wind turbines in IOWA (19 is the FIPS county code for Iowa):

wt_IA <- filter(wind_turbines, t_fips %/% 1000 == 19)

Drop entries with missing longitude or latitude values:

wt_IA <- filter(wt_IA, ! is.na(xlong), ! is.na(ylat))

Some missing year values are encoded as -9999; replace these with NA:

wt_IA <- mutate(wt_IA, p_year = replace(p_year, p_year < 0, NA))

To show the locations of wind turbines on a map, load some map data:

iowa_sf <- sf::st_as_sf(maps::map("county", "iowa", plot = FALSE, fill = TRUE))

To show the locations of wind turbines on a map, load some map data:

iowa_sf <-
    sf::st_as_sf(maps::map("county", "iowa",
                           plot = FALSE,
                           fill = TRUE))
p <- ggplot() +
    geom_sf(data = iowa_sf) +
    ggthemes::theme_map()
p

Locations for all wind turbines in iowa:

p + geom_point(aes(xlong, ylat), data = wt_IA)

Using color to show when the wind turbines were built:

year_brk <-c(0, 2005, 2010, 2015, 2020)
year_lab <- c("before 2005",
              "2005-2009",
              "2010-2014",
              "2015-2020")
wt_IA <-
    mutate(wt_IA,
           year = cut(p_year,
                      breaks = year_brk,
                      labels = year_lab,
                      right = FALSE))
p + geom_point(aes(xlong,
                   ylat,
                   color = year),
               data = wt_IA,
               size = 3)

Cancer Map

The website http://www.cancer-rates.info/ia provides data on cancer incidence for a number of different cancers in Iowa. The data for lung and bronchus cancer in 2011 are available in a csv file in the project.

We can read the file with read_csv from the readr package.

Looking at the file shows some things that need to be cleaned up:

  • Two header lines at the beginning
  • Some footer lines.
  • Some values codes as ~.

The header can be handled by using skip = 2 in the read_csv call:

fname <- "data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv"
d <- read_csv(fname, skip = 2)
## Warning: 3 parsing failures.
## row col  expected    actual                                                                                file
## 101  -- 7 columns 1 columns 'data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv'
## 102  -- 7 columns 1 columns 'data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv'
## 103  -- 7 columns 1 columns 'data/Invasive-Cancer-Incidence-Rates-by-County-in-Iowa-Lung-and-Bronchus-2011.csv'
head(d)
## # A tibble: 6 x 7
##   County  `Population at … Cases `Crude Rate` `Age-adjusted R… `95% Confidence …
##   <chr>              <dbl> <chr> <chr>        <chr>            <chr>            
## 1 Union              12570 20    159.11       115.82           69.86            
## 2 Ringgo…             5098 11    215.77       115.03           54.48            
## 3 Monroe              8044 12    149.18       109.99           56.24            
## 4 Page               15926 25    156.98       109.20           70.12            
## 5 Montgo…            10655 17    159.55       99.45            57.41            
## 6 Adams               3996 6     150.15       95.84            35.14            
## # … with 1 more variable: 95% Confidence Interval-Upper Limit <chr>

Let's focus on a few variables and give them more convenient names:

d <- select(d, county = 1, population = 2, count = 3, crude_rate = 4)

The footer needs to be removed:

tail(d)
## # A tibble: 6 x 4
##   county                                             population count crude_rate
##   <chr>                                                   <dbl> <chr> <chr>     
## 1 Butler                                                  14960 5     33.42     
## 2 Winneshiek                                              21045 ~     ~         
## 3 STATE                                                 3065223 2368  77.25     
## 4 Note: All rates are per 100,000. Rates are age-ad…         NA <NA>  <NA>      
## 5 Rates generated on Jun 12, 2019.                           NA <NA>  <NA>      
## 6 Based on data released Nov 2017.                           NA <NA>  <NA>

One way to remove the footer:

d <- filter(d, ! is.na(population))
d <- filter(d, county != "STATE")
tail(d)
## # A tibble: 6 x 4
##   county     population count crude_rate
##   <chr>           <dbl> <chr> <chr>     
## 1 Lyon            11720 ~     ~         
## 2 Kossuth         15392 7     45.48     
## 3 Palo Alto        9360 ~     ~         
## 4 Grundy          12474 5     40.08     
## 5 Butler          14960 5     33.42     
## 6 Winneshiek      21045 ~     ~

Changing count and crude_rate to numeric changes the ~ entries to missing values (NA) values:

d <- mutate(d, count = as.numeric(count), crude_rate = as.numeric(crude_rate))
## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion

## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion

In this case there are no zero case values; two ways to check:

count(d, count == 0)
## # A tibble: 2 x 2
##   `count == 0`     n
##   <lgl>        <int>
## 1 FALSE           95
## 2 NA               4
any(d$count == 0, na.rm = TRUE)
## [1] FALSE

It might be reasonable to assume these values where zero, so replace them with zeros:

d <- replace_na(d, list(count = 0, crude_rate = 0))

A choropleth map uses color or shading to represent values measured for different geographic regions.

We will need to merge, or left join, the cancer data with the map date we loaded for the wind turbine map.

For Iowa this can be done with the county name, but some care is needed.

d$county[1]
## [1] "Union"
iowa_sf$ID[1]
## [1] "iowa,adair"

d <- mutate(d, cname = county, county = tolower(county))
iowa_sf <- mutate(iowa_sf, county = sub("iowa,", "", ID))

setdiff(d$county, iowa_sf$county)
## [1] "o'brien"
setdiff(iowa_sf$county, d$county)
## [1] "obrien"

d <- mutate(d, county = sub("'", "", county))

setdiff(d$county, iowa_sf$county)
## character(0)
setdiff(iowa_sf$county, d$county)
## character(0)

Define rate1K variable as the number of cases per 1000 inhabitants and left join the data to the polygons:

d <- mutate(d, rate1K = 1000 * (count / population))
md <- left_join(iowa_sf, d, "county")
head(md)
## Simple feature collection with 6 features and 7 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -95.10526 ymin: 40.60552 xmax: -91.06018 ymax: 43.51041
## CRS:           EPSG:4326
##               ID    county population count crude_rate     cname    rate1K
## 1     iowa,adair     adair       7565    10     132.19     Adair 1.3218771
## 2     iowa,adams     adams       3996     6     150.15     Adams 1.5015015
## 3 iowa,allamakee allamakee      14204     7      49.28 Allamakee 0.4928189
## 4 iowa,appanoose appanoose      12863    11      85.52 Appanoose 0.8551660
## 5   iowa,audubon   audubon       6019     5      83.07   Audubon 0.8307028
## 6    iowa,benton    benton      26121    28     107.19    Benton 1.0719345
##                             geom
## 1 MULTIPOLYGON (((-94.24583 4...
## 2 MULTIPOLYGON (((-94.70992 4...
## 3 MULTIPOLYGON (((-91.22634 4...
## 4 MULTIPOLYGON (((-92.63009 4...
## 5 MULTIPOLYGON (((-95.10526 4...
## 6 MULTIPOLYGON (((-92.06286 4...

A simple map:

library(ggthemes)
library(viridis)
## Loading required package: viridisLite
ggplot(md) + geom_sf(aes(fill = rate1K))

An improved version:

library(ggthemes)
library(viridis)
ggplot(md) +
    geom_sf(aes(fill = rate1K),
            color = "grey") +
    scale_fill_viridis(name = "Rate per 1000") +
    theme_map()

A simple interactive version using plotly:

mdl <- mutate(md,
              label = paste(cname, round(rate1K, 1), population, sep = "\n"))
p <- ggplot(mdl) +
    geom_sf(aes(fill = rate1K,
                text = label), 
            color = "grey") +
    scale_fill_viridis(name = "Rate per 1000") +
    theme_map()
## Warning: Ignoring unknown aesthetics: text

plotly::ggplotly(p, tooltip = "text")

The leaflet package supports more sophisticated interactive maps:

library(leaflet)
pal <- colorNumeric(
    palette = "viridis",
    domain = md$rate1K)
lab <- lapply(paste0(tools::toTitleCase(md$county), "<BR>",
                     "Rate: ", round(md$rate1K, 1), "<BR>",
                     "Pop: ", scales::comma(md$population,
                                            accuracy = 1)),
              htmltools::HTML)
leaflet(sf::st_transform(md, 4326)) %>%
    addPolygons(weight = 2,
                color = "grey",
                fillColor = ~ pal(rate1K),
                fillOpacity = 1,
                highlightOptions =
                    highlightOptions(color = "white",
                                     weight = 2,
                                     bringToFront = TRUE),
                label = lab) %>%
    addLegend(pal = pal, values = ~ rate1K)

Unemployment Map

Local Area Unemployment Statistics page from the Bureau of Labor Statistics makes available county-level monthly unemployment data for a 14-month window. The file for February 2020 through March 2021 is available is available at http://www.stat.uiowa.edu/~luke/data/laus/laucntycur14-2020.txt and in the project data folder.

This file is a text file but uses a non-standard separator. It is designed for human readability and uses a comma as a thousands separator or grouping mark. It also includes header and footer information. It is still reasonably easy to read in.

One way to read the data into R is:

lausURL <- "data/laucntycur14-2020.txt"
lausUS <- read.table(lausURL,
                     col.names = c("LAUSAreaCode", "State", "County",
                                   "Title", "Period",
                                   "LaborForce", "Employed",
                                   "Unemployed", "UnempRate"),
                     quote = '"', sep = "|", skip = 6,
                     stringsAsFactors = FALSE, strip.white = TRUE,
                     fill = TRUE)
footstart <- grep("------", lausUS$LAUSAreaCode)
lausUS <- lausUS[1:(footstart - 1),]

It may be useful to be able to access the county name and state name separately:

lausUS <- separate(lausUS, Title, c("cname", "scode"),
                   sep = ", ", fill = "right")

The UnempRate variable is read as character data because of missing value encoding, so needs to be converted to numeric:

lausUS <- mutate(lausUS, UnempRate = as.numeric(UnempRate))
## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion

Check for missing values:

select_if(lausUS, anyNA) %>% names()
## [1] "scode"     "UnempRate"

The state code is missing for the District of Columbia:

select(lausUS, cname, scode) %>%
    filter(is.na(scode)) %>%
    unique()
##                  cname scode
## 1 District of Columbia  <NA>

March and April 2020 numbers were not available for Puerto Rico:

select(lausUS, scode, Period, UnempRate) %>%
    filter(is.na(UnempRate)) %>%
    unique()
##    scode Period UnempRate
## 1     PR Mar-20        NA
## 79    PR Apr-20        NA

To compute the national monthly unemployment rates over this period we need some more data cleaning:

lausUS <- mutate(lausUS,
                 Period = fct_inorder(Period),
                 LaborForce = as.numeric(gsub(",", "", LaborForce)),
                 Unemployed = as.numeric(gsub(",", "", Unemployed)))
## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion

## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion

Unemployment during this period was affected significantly by the COVID-19 pandemic. A plot shows a large spike in April 2020:

group_by(lausUS, Period) %>%
    summarize(Unemployed = sum(Unemployed, na.rm = TRUE),
              LaborForce = sum(LaborForce, na.rm = TRUE),
              UnempRate = 100 * (Unemployed / LaborForce)) %>%
    ggplot(aes(Period, UnempRate, group = 1)) +
    geom_line()

A choropleth map can be used to look at how the impact was distributed across the country.

To show unemployment rates on a map we need to merge the unemployment data with map data.

To match county unemployment data and county shape data it is safer to use the numeric FIPS county code. This can be added with

lausUS <- mutate(lausUS, fips = State * 1000 + County)

Shape data for US counties can be obtained from a number of sources in a number of different formats.

Here is one approach:

counties_sf <- sf::st_as_sf(maps::map("county", plot = FALSE, fill = TRUE))
county.fips <-
    mutate(maps::county.fips, polyname = sub(":.*", "", polyname)) %>%
    unique()
counties_sf <- left_join(counties_sf, county.fips, c("ID" = "polyname"))
states_sf <-  sf::st_as_sf(maps::map("state", plot = FALSE, fill = TRUE))

Some summaries over the period can be computed as

summaryUS <- group_by(lausUS, County, State, fips) %>%
    summarize(avg_unemp = mean(UnempRate, na.rm = TRUE),
              max_unemp = max(UnempRate, na.rm = TRUE),
              apr_unemp = UnempRate[Period == "Apr-20"]) %>%
    ungroup()
## `summarise()` has grouped output by 'County', 'State'. You can override using the `.groups` argument.
head(summaryUS)
## # A tibble: 6 x 6
##   County State  fips avg_unemp max_unemp apr_unemp
##    <int> <int> <dbl>     <dbl>     <dbl>     <dbl>
## 1      1     1  1001      4.65      10.9      10.9
## 2      1     4  4001     12.7       17.6      16.6
## 3      1     5  5001      4.06       5.2       4.8
## 4      1     6  6001      8.8       14.6      14.6
## 5      1     8  8001      8.3       12.7      12.7
## 6      1     9  9001      8.31      11.7       8.2

A choropleth map of the April 2020 unemployment rates:

left_join(counties_sf, summaryUS, "fips") %>%
    ggplot() +
    geom_sf(aes(fill = apr_unemp)) +
    scale_fill_viridis(name = "Rate", na.value = "red") +
    theme_map() +
    geom_sf(data = states_sf, col = "grey", fill = NA)

Using a very visible color for missing data is useful, at least during exploration.

anti_join can show the county geometry that does not have an entry in the unemployment data:

anti_join(counties_sf, summaryUS, "fips")
## Simple feature collection with 1 feature and 2 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -103.0121 ymin: 42.99475 xmax: -102.0782 ymax: 43.68803
## CRS:           EPSG:4326
##                     ID  fips                           geom
## 1 south dakota,shannon 46113 MULTIPOLYGON (((-102.8115 4...

Shannon County, SD (FIPS 46113), was renamed to Oglala Lakota County in June 2015 and given a new FIPS code, 46102.

The geometry data table needs to be updated:

counties_sf <- mutate(counties_sf, fips = replace(fips, fips == 46113, 46102))

With the updated data the map is now complete:

left_join(counties_sf, summaryUS, "fips") %>%
    ggplot() +
    geom_sf(aes(fill = apr_unemp)) +
    scale_fill_viridis(name = "Rate", na.value = "red") +
    theme_map() +
    geom_sf(data = states_sf, col = "grey", fill = NA)

Gapminder Childhood Mortality Data

The gapminder package provides a subset of the data from the Gapminder web site. Additional data sets are available.

  • A data set on childhood mortality is available locally as a csv file or an Excel file. The Excel file is also available in the project data folder.

  • The numbers represent number of deaths within the first five years per 1000 births.

Many researchers like to manage their data in a spreadsheet. Being able to read such a sheet directly greatly helps keeping the workflow reproducible.

Many spreadsheets contain header, footers, and other annotations to aid a human viewer.

As long as the data are in a rectangular region it is usually not hard to extract them programmatically.

Loading the data:

library(readxl)
gcm <- read_excel("data/gapminder-under5mortality.xlsx")
names(gcm)[1]
## [1] "Under five mortality"
names(gcm)[1] <- "country"
head(gcm)
## # A tibble: 6 x 217
##   country         `1800.0` `1801.0` `1802.0` `1803.0` `1804.0` `1805.0` `1806.0`
##   <chr>              <dbl>    <dbl>    <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
## 1 Abkhazia             NA       NA       NA       NA       NA       NA       NA 
## 2 Afghanistan         469.     469.     469.     469.     469.     469.     470.
## 3 Akrotiri and D…      NA       NA       NA       NA       NA       NA       NA 
## 4 Albania             375.     375.     375.     375.     375.     375.     375.
## 5 Algeria             460.     460.     460.     460.     460.     460.     460.
## 6 American Samoa       NA       NA       NA       NA       NA       NA       NA 
## # … with 209 more variables: 1807.0 <dbl>, 1808.0 <dbl>, 1809.0 <dbl>,
## #   1810.0 <dbl>, 1811.0 <dbl>, 1812.0 <dbl>, 1813.0 <dbl>, 1814.0 <dbl>,
## #   1815.0 <dbl>, 1816.0 <dbl>, 1817.0 <dbl>, 1818.0 <dbl>, 1819.0 <dbl>,
## #   1820.0 <dbl>, 1821.0 <dbl>, 1822.0 <dbl>, 1823.0 <dbl>, 1824.0 <dbl>,
## #   1825.0 <dbl>, 1826.0 <dbl>, 1827.0 <dbl>, 1828.0 <dbl>, 1829.0 <dbl>,
## #   1830.0 <dbl>, 1831.0 <dbl>, 1832.0 <dbl>, 1833.0 <dbl>, 1834.0 <dbl>,
## #   1835.0 <dbl>, 1836.0 <dbl>, 1837.0 <dbl>, 1838.0 <dbl>, 1839.0 <dbl>,
## #   1840.0 <dbl>, 1841.0 <dbl>, 1842.0 <dbl>, 1843.0 <dbl>, 1844.0 <dbl>,
## #   1845.0 <dbl>, 1846.0 <dbl>, 1847.0 <dbl>, 1848.0 <dbl>, 1849.0 <dbl>,
## #   1850.0 <dbl>, 1851.0 <dbl>, 1852.0 <dbl>, 1853.0 <dbl>, 1854.0 <dbl>,
## #   1855.0 <dbl>, 1856.0 <dbl>, 1857.0 <dbl>, 1858.0 <dbl>, 1859.0 <dbl>,
## #   1860.0 <dbl>, 1861.0 <dbl>, 1862.0 <dbl>, 1863.0 <dbl>, 1864.0 <dbl>,
## #   1865.0 <dbl>, 1866.0 <dbl>, 1867.0 <dbl>, 1868.0 <dbl>, 1869.0 <dbl>,
## #   1870.0 <dbl>, 1871.0 <dbl>, 1872.0 <dbl>, 1873.0 <dbl>, 1874.0 <dbl>,
## #   1875.0 <dbl>, 1876.0 <dbl>, 1877.0 <dbl>, 1878.0 <dbl>, 1879.0 <dbl>,
## #   1880.0 <dbl>, 1881.0 <dbl>, 1882.0 <dbl>, 1883.0 <dbl>, 1884.0 <dbl>,
## #   1885.0 <dbl>, 1886.0 <dbl>, 1887.0 <dbl>, 1888.0 <dbl>, 1889.0 <dbl>,
## #   1890.0 <dbl>, 1891.0 <dbl>, 1892.0 <dbl>, 1893.0 <dbl>, 1894.0 <dbl>,
## #   1895.0 <dbl>, 1896.0 <dbl>, 1897.0 <dbl>, 1898.0 <dbl>, 1899.0 <dbl>,
## #   1900.0 <dbl>, 1901.0 <dbl>, 1902.0 <dbl>, 1903.0 <dbl>, 1904.0 <dbl>,
## #   1905.0 <dbl>, 1906.0 <dbl>, …

This data set is in wide format.

A long version is useful for working with ggplot.

tgcm <- pivot_longer(gcm, -1, names_to = "year", values_to = "u5mort")
head(tgcm)
## # A tibble: 6 x 3
##   country  year   u5mort
##   <chr>    <chr>   <dbl>
## 1 Abkhazia 1800.0     NA
## 2 Abkhazia 1801.0     NA
## 3 Abkhazia 1802.0     NA
## 4 Abkhazia 1803.0     NA
## 5 Abkhazia 1804.0     NA
## 6 Abkhazia 1805.0     NA
tgcm <- mutate(tgcm, year = as.numeric(year))
head(tgcm)
## # A tibble: 6 x 3
##   country   year u5mort
##   <chr>    <dbl>  <dbl>
## 1 Abkhazia  1800     NA
## 2 Abkhazia  1801     NA
## 3 Abkhazia  1802     NA
## 4 Abkhazia  1803     NA
## 5 Abkhazia  1804     NA
## 6 Abkhazia  1805     NA

Some explorations:

p <- ggplot(tgcm) +
    geom_line(aes(year, u5mort, group = country), alpha = 0.3)
p
## Warning: Removed 18644 row(s) containing missing values (geom_path).

plotly::ggplotly(p)

Some selected countries:

countries <- c("United States", "United Kingdom", "Germany", "China", "Egypt")
filter(tgcm, country %in% countries) %>%
    ggplot() +
    geom_line(aes(x = year, y = u5mort, color = country))

Examining the missing values:

tgcm_miss <-
    group_by(tgcm, country) %>%
    summarize(anyNA = any(is.na(u5mort))) %>%
    filter(anyNA) %>%
    pull(country)

p <- ggplot(filter(tgcm, country %in% tgcm_miss)) +
    geom_line(aes(x = year, y = u5mort, group = country), na.rm = TRUE)
p

plotly::ggplotly(p)
LS0tCnRpdGxlOiAiQmFzaWMgRGF0YSBXcmFuZ2xpbmcgYW5kIERhdGEgVmlzdWFsaXphdGlvbiBpbiBSIgphdXRob3I6ICJMdWtlIFRpZXJuZXkiCmRhdGU6ICIyMSBKdW5lLCAyMDIxIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKYGBge3IgZ2xvYmFsX29wdGlvbnMsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChjb2xsYXBzZT1UUlVFKQpgYGAKCiMjIEludHJvZHVjdGlvbgoKSW4gdGhpcyBjbGFzcyBJIHdpbGwKCi0gQnJpZWZseSBvdXRsaW5lIHRoZSBoaXN0b3J5IG9mIFIuCi0gVXNpbmcgc29tZSBleGFtcGxlcyBicmllZmx5IHNob3cgaG93IHRvIGRvIGRhdGEgd3JhbmdsaW5nCiAgYW5kIHZpc3VhbGl6ZSBkYXRhIGluIFIuCiAKTWF0ZXJpYWxzIGZvciB0aGlzIGNsYXNzIGFyZSBhdmFpbGFibGUgb24gR2l0SHViIGF0CjxodHRwczovL2dpdGh1Yi5jb20vbHRpZXJuZXkvU0lCUy1XVi0yMDIxLmdpdD4uCgoqIFlvdSBjYW4gYWNjZXNzIGl0IGFzIGFuIFJTdHVkaW8gcHJvamVjdCBieSBmb2xsb3dpbmcgdGhlIG1lbnUgc2VsZWN0aW9uCiAgKipGaWxlID4gTmV3IFByb2plY3QgPiBWZXJzaW9uIENvbnRyb2wgPiBHaXQqKiBhbmQgc3BlY2lmeWluZyB0aGlzIFVSTC4KKiBZb3UgY2FuIHVzZSB0aGUgYGdpdGAgY29tbWFuZCBsaW5lIGNsaWVudCB3aXRoCiAgICBgYGBzaGVsbApnaXQgY2xvbmUgaHR0cHM6Ly9naXRodWIuY29tL2x0aWVybmV5L1NJQlMtV1YtMjAyMS5naXQKICAgIGBgYAoKTWF0ZXJpYWxzIGZvciBvdXIgX0RhdGEgVmlzdWFsaXphdGlvbiBhbmQgRGF0YQpUZWNobm9sb2dpZXNfIGNvdXJzZSBhcmUgYXZhaWxhYmxlIGF0CiA8aHR0cDovL3d3dy5zdGF0LnVpb3dhLmVkdS9+bHVrZS9jbGFzc2VzL1NUQVQ0NTgwLTIwMjEvPgoKU29tZSB0b29scyBJIHdpbGwgYmUgdXNpbmc6CgoqIFRoZSBbUlN0dWRpb10oaHR0cHM6Ly93d3cucnN0dWRpb24uY29tKSBJREUuCiogTWFueSBmZWF0dXJlcyBmcm9tIHRoZSBiYXNpYyBbUl0oaHR0cHM6Ly93d3cuci1wcm9qZWN0Lm9yZykgZGlzdHJpYnV0aW9uLgoqIFNvbWUgdG9vbHMgZnJvbSB0aGUgW190aWR5dmVyc2VfXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykuCiogVGhlIFtgZ2dwbG90YF0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvKSBwYWNrYWdlIGJhc2VkIG9uCiAgdGhlIF9HcmFtbWFyIG9mIEdyYXBoaWNzXyBmcmFtZXdvcmsuCgpNb3N0IG9mIHRoZSBwYWNrYWdlcyBhcmUgbG9hZGVkIGJ5IGxvYWRpbmcgdGhlIGB0aWR5dmVyc2VgIHBhY2thZ2U6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgpVc2VmdWwgcmVmZXJlbmNlczoKCj4gSGFkbGV5IFdpY2toYW0gYW5kIEdhcnJldHQgR3JvbGVtdW5kICgyMDE2KSwgW19SIGZvciBEYXRhCj4gU2NpZW5jZV9dKGh0dHA6Ly9yNGRzLmhhZC5jby5uei8pLCBPJ1JlaWxseS4KCj4gQ2xhdXMgTy4gV2lsa2UgKDIwMTkpLCBbX0Z1bmRhbWVudGFscyBvZiBEYXRhCj4gIFZpc3VhbGl6YXRpb25fXShodHRwczovL3NlcmlhbG1lbnRvci5jb20vZGF0YXZpei8pLCBPJ1JlaWxseS4KICAKPiBLaWVyYW4gSGVhbHkgKDIwMTgpIFtfRGF0YSBWaXN1YWxpemF0aW9uOiBBIHByYWN0aWNhbAo+IGludHJvZHVjdGlvbl9dKGh0dHA6Ly9zb2N2aXouY28vKSwgUHJpbmNldG9uCgo+IFJhZmFlbCBBLiBJcml6YXJyeSAoMjAxOSksIFtJbnRyb2R1Y3Rpb24gdG8gRGF0YSBTY2llbmNlOiBfRGF0YQo+IEFuYWx5c2lzIGFuZCBQcmVkaWN0aW9uIEFsZ29yaXRobXMgd2l0aAo+IFJfXShodHRwczovL3JhZmFsYWIuZ2l0aHViLmlvL2RzYm9vay8pLCBDaGFwbWFuICYgSGFsbC9DUkMuIChbQm9vawo+IHNvdXJjZSBvbiBHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9yYWZhbGFiL2RzYm9vaykpCgoKQXNrIHF1ZXN0aW9ucyBhbnkgdGltZSEKCiAKIyMjIFRoZSBSIExhbmd1YWdlCgpSIGlzIGEgbGFuZ3VhZ2UgZm9yIGRhdGEgYW5hbHlzaXMgYW5kIGdyYXBoaWNzLgoKKiBSIHdhcyBvcmlnaW5hbGx5IGRldmVsb3BlZCBieSBSb2JlcnQgR2VudGxlbWFuIGFuZCBSb3NzIEloYWthIGluIHRoZQogIGVhcmx5IDE5OTAncyBmb3IgYSBNYWNpbnRvc2ggY29tcHV0ZXIgbGFiIGF0IFUuIG9mIEF1Y2tsYW5kLCBOZXcgWmVhbGFuZC4KCiogUiBpcyBiYXNlZCBvbiB0aGUgUyBsYW5ndWFnZSBkZXZlbG9wZWQgYnkgSm9obiBDaGFtYmVycyBhbmQKICBvdGhlcnMgYXQgQmVsbCBMYWJzLgoKUiBpcyBhbiBPcGVuIFNvdXJjZSBwcm9qZWN0LgoKKiBTaW5jZSAxOTk3IFIgaXMgZGV2ZWxvcGVkIGFuZCBtYWludGFpbmVkIGJ5IHRoZSBSLWNvcmUgZ3JvdXAsCiAgd2l0aCBhcm91bmQgMjAgbWVtYmVycyBsb2NhdGVkIGluIG1hb3IgdGhhbiAxMCBkaWZmZXJlbnQgY291bnRyaWVzLgoKKiBSIGlzIHdpZGVseSB1c2VkIGluIHRoZSBmaWVsZCBvZiBzdGF0aXN0aWNzIGFuZCBiZXlvbmQsIGVzcGVjaWFsbHkgaW4KICB1bml2ZXJzaXR5IGVudmlyb25tZW50cy4KCiogUiBoYXMgYmVjb21lIHRoZSBwcmltYXJ5IGZyYW1ld29yayBmb3IgZGV2ZWxvcGluZyBhbmQgbWFraW5nIGF2YWlsYWJsZQogIG5ldyBzdGF0aXN0aWNhbCBtZXRob2RvbG9neS4KCiogTWFueSAobm93IG92ZXIgMTcsMDAwKSBleHRlbnNpb24gcGFja2FnZXMgYXJlIGF2YWlsYWJsZSB0aHJvdWdoIENSQU4gb3IKICBzaW1pbGFyIHJlcG9zaXRvcmllcy4KCiMjIyBXb3JraW5nIHdpdGggUgoKUiBpcyBkZXNpZ25lZCBmb3IgaW50ZXJhY3RpdmUgZGF0YSBleHBsb3JhdGlvbi4KCiogSW50ZXJhY3Rpb24gaXMgdGhyb3VnaCBhIF9yZWFkLWV2YWwtcHJpbnQgbG9vcCAoUkVQTClfLgoqIFRoaXMgaXMgYWxzbyBjYWxsZWQgYSBfY29tbWFuZCBsaW5lIGludGVyZmFjZSAoQ0xJKV8uCgpBbGwgY29tcHV0YXRpb25zIGFyZSBzcGVjaWZpZWQgaW4gdGhlIFIgbGFuZ3VhZ2UuCgoqIEV2ZW4gZm9yIHNpbXBsZSB0YXNrcyB5b3UgbmVlZCB0byBrbm93IGEgbGl0dGxlIG9mIHRoZSBsYW5ndWFnZS4KKiBBZnRlciBsZWFybmluZyB0byBkbyBzaW1wbGUgdGFza3MgeW91IGtub3cgc29tZSBvZiB0aGUgbGFuZ3VhZ2UuCgpUaGUgbGFuZ3VhZ2UgaXMgdXNlZCB0bwogICAgCiogcHJlcGFyZSBkYXRhIGZvciBhbmFseXNpczsKKiBzcGVjaWZ5IGluZGl2aWR1YWwgYW5hbHlzZXM7CiogcHJvZ3JhbSByZXBlYXRlZCBvciBzaW1pbGFyIGFuYWx5c2VzOwoqIHByb2dyYW0gbmV3IG1ldGhvZHMgb2YgYW5hbHlzaXMuCgpTcGVjaWZ5aW5nIHRoZXNlIHRhc2tzIGluIGEgbGFuZ3VhZ2Ugc3VwcG9ydHMgX3JlcHJvZHVjaWJsZSByZXNlYXJjaF8uCgpUaGUgUiBsYW5ndWFnZSBvcGVyYXRlcyBvbiB2ZWN0b3JzIGFuZCBhcnJheXMuCgpDb21tb25seSB1c2VkIGRhdGEgdHlwZXMgYXJlOgoKKiBpbnRlZ2VyIGFuZCBudW1lcmljIHZlY3RvcnM7CiogbG9naWNhbCB2ZWN0b3JzOwoqIGNoYXJhY3RlciB2ZWN0b3JzOwoqIGZhY3RvcnMuCgpBbGwgYmFzaWMgdmVjdG9yIHR5cGVzIHN1cHBvcnQgbWlzc2luZyAoYE5BYCkgdmFsdWVzLgoKQXJpdGhtZXRpYyBvcGVyYXRpb25zIGFyZSB2ZWN0b3JpemVkIHRvIG9wZXJhdGUgZWxlbWVudC13aXNlIG9uIHZlY3RvcnMuCgpEYXRhIHZlY3RvcnMgYXJlIHVzdWFsbHkgY29tYmluZWQgaW50byB0YWJsZS1saWtlIG9iamVjdHMgY2FsbGVkIF9kYXRhCmZyYW1lc18uCgoKIyMjIFRoZSBEYXRhIEFuYWx5c2lzIFByb2Nlc3MKCkEgZmlndXJlIHRoYXQgc2hvd3MgdGhlIHN0ZXBzIHVzdWFsbHkgaW52b2x2ZWQgaW4gYSBkYXRhIGFuYWx5c2lzCnByb2plY3Q6CgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpsaWJyYXJ5KG5vbW5vbWwpCmBgYAo8Y2VudGVyPgpgYGB7bm9tbm9tbCwgZWNobyA9IEZBTFNFLCBmaWcuaGVpZ2h0ID0gMy41fQojcGFkZGluZzogMjUKI2ZvbnRzaXplOiAxOAojZmlsbDogI0UxREFGRjsgI0Q0QTlGRgojc3Ryb2tlOiAjODUxNUM3CiNsaW5ld2lkdGg6IDIKCltJbXBvcnRdIC0+IFtVbmRlcnN0YW5kXQpbVW5kZXJzdGFuZCB8CiAgW1dyYW5nbGVdIC0+IFtWaXN1YWxpemVdCiAgW1Zpc3VhbGl6ZV0gLT4gW01vZGVsXQogIFtNb2RlbF0gLT4gW1dyYW5nbGVdCl0KW1VuZGVyc3RhbmRdIC0+IFtDb21tdW5pY2F0ZV0KYGBgCjwvY2VudGVyPgoKVGhlc2Ugc3RlcHMgYXJlIG9mdGVuIHJlcGVhdGVkIG1hbnkgdGltZXMsIHNvIGl0IGlzIGltcG9ydGFudCB0byBtYWtlCnlvdXIgd29yayByZXByb2R1Y2libGUuCgoKIyMjIFJlcHJvZHVjaWJsZSBEYXRhIEFuYWx5c2lzCgpNYWtpbmcgeW91ciB3b3JrIHJlcHJvZHVjaWJsZToKCiogU2F2ZSB5b3Ugd29yayBpbiBhIHRleHQgZmlsZSBvciBub3RlYm9vay4KCiogVHJhY2sgY2hhbmdlcyB0byB5b3VyIGZpbGVzIHdpdGggYSB2ZXJzaW9uIGNvbnRyb2wgc3lzdGVtIGxpa2UKICBbYGdpdGBdKGh0dHBzOi8vZ2l0LXNjbS5jb20vKS4KCiogVXNlIGEgc3lzdGVtIGxpa2UgW1JtYXJrZG93bl0oaHR0cHM6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20pIHRvCiAgcHJlcGFyZSB5b3VyIHJlcG9ydHMuCgpUaGlzIGFsbG93cyB5b3UgdG8gcmUtY3JlYXRlIHlvdXIgcmVwb3J0IHdoZW4gZGF0YSBjaGFuZ2VzIChhcyBpdApvZnRlbiB3aWxsISkuCgpBIGdvb2QgcmVzb3VyY2UgZm9yIHNldHRpbmcgdXAgeW91ciB0b29scyB0byBzdXBwb3J0IHRoaXMgaXMgW19IYXBweQpHaXQgYW5kIEdpdEh1YiBmb3IgdGhlIHVzZVJfXShodHRwczovL2hhcHB5Z2l0d2l0aHIuY29tLykuCgoKIyMgU29tZSBFeGFtcGxlcwoKV29ya2luZyB3aXRoIHJlc2VhcmNoIGRhdGEgYSBmaXJzdCBzdGVwIGlzIHVzdWFsbHkgdG8gcmVhZCBhbmQgY2xlYW4KdGhlIGRhdGEuCgpXZSdsbCBwdXQgdGhhdCBvZmYgZm9yIGEgbGl0dGxlIHdoaWxlIGFuZCB3b3JrIHdpdGggc29tZSBkYXRhIHNldHMKbWFkZSBhdmFpbGFibGUgaW4gUiBwYWNrYWdlcy4KCkRhdGEgc2V0cyBhdmFpbGFibGUgaW4gUiBwYWNrYWdlcyBpbmNsdWRlOgoKKiBtYW55IGNsYXNzaWMgZGF0YSBzZXRzOwoqIG5ld2VyLCBvZnRlbiBsYXJnZXIsIGRhdGEgc2V0cyB1c2VmdWwgZm9yIGxlYXJuaW5nOwoqIGN1cnJlbnQgZGF0YSBvYnRhaW5lZCBieSBxdWVyeWluZyB3ZWIgQVBJcy4KCgojIyMgT2xkIEZhaXRoZnVsIEVydXB0aW9ucwoKQSBzaW1wbGUgY2xhc3NpYyBkYXRhIHNldCBpcyB0aGUgYGdleXNlcmAgZGF0YSBmcmFtZSBhdmFpbGFibGUgaW4KcGFja2FnZSBgTUFTU2AKCmBgYHtyfQpkYXRhKGdleXNlciwgcGFja2FnZSA9ICJNQVNTIikKZGltKGdleXNlcikKaGVhZChnZXlzZXIpCmBgYAoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+IGBoZWFkYCBhbmQgYHRhaWxgIHJldHVybiB0aGUgZmlyc3QgYW5kCmxhc3QgZmV3IHJvd3Mgb2YgYSBkYXRhIGZyYW1lLgoKVGhleSBhcmUgdXNlZnVsIGZvciBxdWljayBzYW5pdHkgY2hlY2tzLiAgPC9kaXY+CgpUaGUgcm93cyByZXByZXNlbnQgbWVhc3VyZW1lbnRzIHJlY29yZGVkIGZvciBlcnVwdGlvbnMgb2YgdGhlIF9PbGQKRmFpdGhmdWxfIGdleXNlciBpbiBZZWxsb3dzdG9uZSBOYXRpb25hbCBQYXJrLCBXeW9taW5nLgoKVGhlIHZhcmlhYmxlcyBhcmU6CgoqIGB3YWl0aW5nYDogdGhlIHRpbWUgaW4gbWludXRlcyBzaW5jZSB0aGUgcHJldmlvdXMgZXJ1cHRpb247CiogYGR1cmF0aW9uYDogdGhlIGR1cmF0aW9uIG9mIHRoZSBlcnVwdGlvbi4KClRoZSBkdXJhdGlvbnMgaGF2ZSBhIGJpbW9kYWwgZGlzdHJpYnV0aW9uOgoKYGBge3J9CmdncGxvdChnZXlzZXIpICsKICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gZHVyYXRpb24pLCBiaW5zID0gMTUsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJncmV5IikKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gQSBiYXNpYyB0ZW1wbGF0ZSBmb3IgY3JlYXRpbmcgYSBwbG90CndpdGggYGdncGxvdGA6CgpgYGByCmdncGxvdChkYXRhID0gPERBVEE+KSArIDxHRU9NPihtYXBwaW5nID0gYWVzKDxNQVBQSU5HUz4pKQpgYGAKPC9kaXY+CgpBbiBpbnRlcmVzdGluZyBxdWVzdGlvbiBpcyB3aGV0aGVyIHRoZSBkdXJhdGlvbiBjYW4gYmUgdXNlZCB0byBwcmVkaWN0CndoZW4gdGhlIF9uZXh0XyBlcnVwdGlvbiB3aWxsIG9jY3VyLgoKQSBwbG90IG9mIHRoZSBfcHJldmlvdXNfIGR1cmF0aW9uIGFnYWluc3QgdGhlIHdhaXRpbmcgdGltZSB0byB0aGUKY3VycmVudCBlcnVwdGlvbjoKCmBgYHtyfQpnZ3Bsb3QoZ2V5c2VyKSArIGdlb21fcG9pbnQoYWVzKHggPSBsYWcoZHVyYXRpb24pLCB5ID0gd2FpdGluZykpCmBgYAoKSXQgbG9va3MgbGlrZSBhIHVzZWZ1bCBydWxlIHdvdWxkIGJlIHRvIGV4cGVjdCBhIHNob3J0ZXIgd2FpdGluZyB0aW1lCmFmdGVyIGEgc2hvcnRlciBlcnVwdGlvbi4KCkFuIGludGVyZXN0aW5nIGZlYXR1cmU6IE1hbnkgZHVyYXRpb25zIGFyZSByZWNvcmRlZCBhcyAyIG9yIDQgbWludXRlcy4KVGhpcyBjYW4gYWxzbyBiZSBzZWVuIGluIGEgaGlzdG9ncmFtIHdpdGggbWFueSBzbWFsbCBiaW5zOgoKYGBge3J9CnAgPC0gZ2dwbG90KGdleXNlcikgKwogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBkdXJhdGlvbiwgeSA9IHN0YXQoZGVuc2l0eSkpLAogICAgICAgICAgICAgICAgICAgZmlsbCA9ICJncmV5IiwgY29sb3IgPSAiYmxhY2siLCBiaW5zID0gNTApCnAKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gYGdncGxvdGAgcHJvZHVjZXMgYSBwbG90Cm9iamVjdC4gRHJhd2luZyBvbmx5IGhhcHBlbnMgd2hlbiB0aGUgb2JqZWN0IGlzIHByaW50ZWQuICA8L2Rpdj4KCkRvZXMgdGhpcyByb3VuZGluZyBtYXR0ZXI/CgoqIEZvciBtYW55IGFuYWx5c2VzIGl0IHByb2JhYmx5IGRvZXNuJ3QuCiogSXQgbWlnaHQgaWYgeW91IHdhbnRlZCB0byBmaXQgbm9ybWFsIGRpc3RyaWJ1dGlvbnMgdG8gdGhlIHR3byBncm91cHMuCgpUYWtpbmcgMyBtaW51dGVzIGFzIHRoZSBkaXZpZGUgYmV0d2VlbiBzaG9ydCBhbmQgbG9uZyBkdXJhdGlvbnMgd2UgY2FuCmNvbXB1dGUgdGhlIG1lYW5zIGFuZCBzdGFuZGFyZCBkZXZpYXRpb25zIGFzCgpgYGB7cn0KZCA8LSBnZXlzZXIkZHVyYXRpb24KZF9zaG9ydCA8LSBkW2QgPCAzXQpkX2xvbmcgPC0gZFtkID49IDNdCm1lYW4oZF9zaG9ydCkKc2QoZF9zaG9ydCkKbWVhbihkX2xvbmcpCnNkKGRfbG9uZykKbWVhbihkID49IDMpCmBgYAoKQW4gYXBwcm9hY2ggdGhhdCBzY2FsZXMgYmV0dGVyIGlzIHRvIGNvbXB1dGUgZ3JvdXAgc3VtbWFyaWVzIHVzaW5nCnRvb2xzIGZyb20gdGhlIGBkcGx5cmAgYHRpZHl2ZXJzZWAgcGFja2FnZS4KCkZpcnN0LCBhZGQgYSBgdHlwZWAgdmFyaWFibGU6CgpgYGB7cn0KZ2V5c2VyIDwtIG11dGF0ZShnZXlzZXIsIHR5cGUgPSBpZmVsc2UoZHVyYXRpb24gPCAzLCAic2hvcnQiLCAibG9uZyIpKQpgYGAKClRoZSBzdW1tYXJpZXMgY2FuIHRoZW4gYmUgY29tcHV0ZWQgYXMKCmBgYHtyfQpzZ2QgPC0gc3VtbWFyaXplKGdyb3VwX2J5KGdleXNlciwgdHlwZSksCiAgICAgICAgICAgICAgICAgbWVhbiA9IG1lYW4oZHVyYXRpb24pLAogICAgICAgICAgICAgICAgIHNkID0gc2QoZHVyYXRpb24pLAogICAgICAgICAgICAgICAgIG4gPSBuKCkpCihzZ2QgPC0gbXV0YXRlKHNnZCwgcHJvcCA9IG4gLyBzdW0obikpKQpgYGAKClRoaXMgY29tcHV0YXRpb24gY2FuIGFsc28gYmUgd3JpdHRlbiB1c2luZyB0aGUgX2ZvcndhcmQgcGlwZSBvcGVyYXRvcl8gYCU+JWA6CgpgYGB7cn0Kc2dkIDwtCiAgICBncm91cF9ieShnZXlzZXIsIHR5cGUpICU+JQogICAgc3VtbWFyaXplKG1lYW4gPSBtZWFuKGR1cmF0aW9uKSwKICAgICAgICAgICAgICBzZCA9IHNkKGR1cmF0aW9uKSwKICAgICAgICAgICAgICBuID0gbigpKSAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIG11dGF0ZShwcm9wID0gbiAvIHN1bShuKSkKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gVGhlIHBpcGUgb3BlcmF0b3IgYWxsb3dzIGEgc2VxdWVuY2Ugb2YKb3BlcmF0aW9ucyB0byBiZSBjaGFpbmVkIHRvZ2V0aGVyLgoKVGhlIGxlZnQtaGFuZCBvcGVyYXRpb24gaXMgcGFzc2VkIGltcGxpY2l0bHkgYXMgdGhlIGZpcnN0IGFyZ3VtZW50IHRvCnRoZSBmdW5jdGlvbiBjYWxsZWQgb24gdGhlIHJpZ2h0LiA8L2Rpdj4KCk9uZSB3YXkgdG8gc2hvdyB0aGUgc3VwZXJpbXBvc2VkIG5vcm1hbCBkZW5zaXRpZXM6CgpgYGB7cn0KZjEgPC0gZnVuY3Rpb24oeCkKICAgIHNnZCRwcm9wWzFdICogZG5vcm0oeCwgc2dkJG1lYW5bMV0sIHNnZCRzZFsxXSkKZjIgPC0gZnVuY3Rpb24oeCkKICAgIHNnZCRwcm9wWzJdICogZG5vcm0oeCwgc2dkJG1lYW5bMl0sIHNnZCRzZFsyXSkKcCA8LSBwICsKICAgIHN0YXRfZnVuY3Rpb24oY29sb3IgPSAicmVkIiwgZnVuID0gZjEpICsKICAgIHN0YXRfZnVuY3Rpb24oY29sb3IgPSAiYmx1ZSIsIGZ1biA9IGYyKQpwCmBgYAoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+IEEgYGdncGxvdGAgY2FuIGNvbnNpc3Qgb2Ygc2V2ZXJhbApfbGF5ZXJzXy4gIDwvZGl2PgoKVGhlIG1lYW5zIGFuZCBzdGFuZGFyZCBkZXZpYXRpb25zIGFyZSBhZmZlY3RlZCBieSB0aGUKcm91bmRpbmcuIFN1bW1hcmllcyB0aGF0IG9taXQgdmFsdWVzIGVxdWFsIHRvIDIgb3IgNCBtaW51dGVzIGNhbiBiZQpjb21wdXRlZCBhcwoKYGBge3J9CmdleXNlcjIgPC0gZmlsdGVyKGdleXNlciwgZHVyYXRpb24gIT0gMiwgZHVyYXRpb24gIT0gNCkKc2dkMiA8LQogICAgZ3JvdXBfYnkoZ2V5c2VyMiwgdHlwZSkgJT4lCiAgICBzdW1tYXJpemUobWVhbiA9IG1lYW4oZHVyYXRpb24pLAogICAgICAgICAgICAgIHNkID0gc2QoZHVyYXRpb24pLAogICAgICAgICAgICAgIG4gPSBuKCkpICU+JQogICAgdW5ncm91cCgpICU+JQogICAgbXV0YXRlKHByb3AgPSBuIC8gc3VtKG4pKQpzZ2QyCmBgYAoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+IGBzdW1tYXJpemVgLCBgZ3JvdXBfYnlgLCBhbmQgYG11dGF0ZWAKYXJlIGZyb20gdGhlIGBkcGx5cmAgcGFja2FnZSB0aGF0IGltcGxlbWVudHMgYSBfZ3JhbW1hciBvZiBkYXRhCm1hbmlwdWxhdGlvbl8uICA8L2Rpdj4KCkEgcGxvdCBzaG93aW5nIGN1cnZlcyBjb21wdXRlZCBib3RoIHdheXM6CgpgYGB7cn0KZjFfMiA8LSBmdW5jdGlvbih4KQogICAgc2dkMiRwcm9wWzFdICogZG5vcm0oeCwgc2dkMiRtZWFuWzFdLCBzZ2QyJHNkWzFdKQpmMl8yIDwtIGZ1bmN0aW9uKHgpCiAgICBzZ2QyJHByb3BbMl0gKiBkbm9ybSh4LCBzZ2QyJG1lYW5bMl0sIHNnZDIkc2RbMl0pCnAgPC0gcCArCiAgICBzdGF0X2Z1bmN0aW9uKGNvbG9yID0gInJlZCIsCiAgICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMiwKICAgICAgICAgICAgICAgICAgZnVuID0gZjFfMikgKwogICAgc3RhdF9mdW5jdGlvbihjb2xvciA9ICJibHVlIiwKICAgICAgICAgICAgICAgICAgbGluZXR5cGUgPSAyLAogICAgICAgICAgICAgICAgICBmdW4gPSBmMl8yKQpwCmBgYAoKYGBge3IsIGV2YWwgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQojIyBGYW5jaWVyIHZlcnNpb24gdGhhdCBnZXRzIGEgY29sb3IgbGVnZW5kLgojIyBDb3VsZCBhbHNvIGdldCBhIGxpbmUgdHlwZSBsZWdlbmQuCnAgPC0gZ2dwbG90KGdleXNlcikgKwogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBkdXJhdGlvbiwgeSA9IHN0YXQoZGVuc2l0eSkpLAogICAgICAgICAgICAgICAgICAgZmlsbCA9ICJncmV5IiwgY29sb3IgPSAiYmxhY2siLCBiaW5zID0gNTApCnAgPC0gcCArIAogICAgc3RhdF9mdW5jdGlvbihhZXMoY29sb3IgPSB0eXBlKSwKICAgICAgICAgICAgICAgICAgZGF0YSA9IGZpbHRlcihzZ2QsIHR5cGUgPT0gImxvbmciKSwKICAgICAgICAgICAgICAgICAgZnVuID0gZnVuY3Rpb24oeCkKICAgICAgICAgICAgICAgICAgICAgICAgICBzZ2QkcHJvcFsxXSAqIGRub3JtKHgsIHNnZCRtZWFuWzFdLCBzZ2Qkc2RbMV0pKSArCiAgICBzdGF0X2Z1bmN0aW9uKGFlcyhjb2xvciA9IHR5cGUpLAogICAgICAgICAgICAgICAgICBkYXRhID0gZmlsdGVyKHNnZCwgdHlwZSA9PSAic2hvcnQiKSwKICAgICAgICAgICAgICAgICAgZnVuID0gZnVuY3Rpb24oeCkKICAgICAgICAgICAgICAgICAgICAgICAgICBzZ2QkcHJvcFsyXSAqIGRub3JtKHgsIHNnZCRtZWFuWzJdLCBzZ2Qkc2RbMl0pKQpwCgpwIDwtIHAgKwogICAgIHN0YXRfZnVuY3Rpb24oYWVzKGNvbG9yID0gdHlwZSksCiAgICAgICAgICAgICAgICAgIGRhdGEgPSBmaWx0ZXIoc2dkMiwgdHlwZSA9PSAibG9uZyIpLAogICAgICAgICAgICAgICAgICBsaW5ldHlwZSA9IDIsCiAgICAgICAgICAgICAgICAgIGZ1biA9IGZ1bmN0aW9uKHgpCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2dkMiRwcm9wWzFdICogZG5vcm0oeCwgc2dkMiRtZWFuWzFdLCBzZ2QyJHNkWzFdKSkgKwogICAgc3RhdF9mdW5jdGlvbihhZXMoY29sb3IgPSB0eXBlKSwKICAgICAgICAgICAgICAgICAgZGF0YSA9IGZpbHRlcihzZ2QyLCB0eXBlID09ICJzaG9ydCIpLAogICAgICAgICAgICAgICAgICBsaW5ldHlwZSA9IDIsCiAgICAgICAgICAgICAgICAgIGZ1biA9IGZ1bmN0aW9uKHgpCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2dkMiRwcm9wWzJdICogZG5vcm0oeCwgc2dkMiRtZWFuWzJdLCBzZ2QyJHNkWzJdKSkKcApgYGAKCgojIyMgTWlubmVzb3RhIEJhcmxleSBZaWVsZHMKCkEgY2xhc3NpYyBkYXRhIHNldDogVG90YWwgeWllbGQgaW4gYnVzaGVscyBwZXIgYWNyZSBmb3IgMTAgdmFyaWV0aWVzCmF0IDYgc2l0ZXMgaW4gTWlubmVzb3RhIGluIGVhY2ggb2YgdHdvIHllYXJzLCAxOTMxIGFuZCAxOTMyLgoKVGhlIHJhdyBkYXRhOgoKYGBge3J9CmRhdGEoYmFybGV5LCBwYWNrYWdlID0gImxhdHRpY2UiKQpoZWFkKGJhcmxleSkKYGBgCgpTb21lIGluaXRpYWwgcGxvdHM6CgpgYGB7ciwgZmlnLndpZHRoID0gMTB9CnAxIDwtIGdncGxvdChiYXJsZXkpICsgZ2VvbV9wb2ludChhZXMoeCA9IHlpZWxkLCB5ID0gdmFyaWV0eSkpCnAyIDwtIGdncGxvdChiYXJsZXkpICsgZ2VvbV9wb2ludChhZXMoeCA9IHlpZWxkLCB5ID0gc2l0ZSkpCmNvd3Bsb3Q6OnBsb3RfZ3JpZChwMSwgcDIpCmBgYAoKVXNpbmcgY29sb3IgdG8gc2VwYXJhdGUgeWllbGRzIGluIHRoZSB0d28geWVhcnM6CgpgYGB7ciwgZmlnLndpZHRoID0gMTB9CnAxIDwtIGdncGxvdChiYXJsZXkpICsgZ2VvbV9wb2ludChhZXMoeCA9IHlpZWxkLCB5ID0gdmFyaWV0eSwgY29sb3IgPSB5ZWFyKSkKcDIgPC0gZ2dwbG90KGJhcmxleSkgKyBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSBzaXRlLCBjb2xvciA9IHllYXIpKQpjb3dwbG90OjpwbG90X2dyaWQocDEsIHAyKQoKYGBgCgpDYW4gd2UgYWxzbyBzaG93IGBzaXRlYCB1c2luZyBzeW1ib2wgc2hhcGU/CgpgYGB7cn0KZ2dwbG90KGJhcmxleSkgKwogICAgZ2VvbV9wb2ludChhZXMoeCA9IHlpZWxkLCB5ID0gdmFyaWV0eSwgY29sb3IgPSB5ZWFyLCBzaGFwZSA9IHNpdGUpKQpgYGAKClRoZXJlIGlzIGEgbG90IG9mIF9pbnRlcmZlcmVuY2VfIGJldHdlZW4gc2hhcGUgYW5kIGNvbG9yLgoKUG9zc2libGUgaW1wcm92ZW1lbnRzOgoKKiBqaXR0ZXJpbmc7CiogbGFyZ2VyIHBvaW50cy4KCmBgYHtyfQpnZ3Bsb3QoYmFybGV5KSArCiAgICBnZW9tX3BvaW50KGFlcyh4ID0geWllbGQsIHkgPSB2YXJpZXR5LCBjb2xvciA9IHllYXIsIHNoYXBlID0gc2l0ZSksCiAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKGhlaWdodCA9IDAuMTUsIHdpZHRoID0gMCksCiAgICAgICAgICAgICAgIHNpemUgPSAyKQpgYGAKCkFub3RoZXIgYXBwcm9hY2g6IF9mYWNldGluZ18gdG8gcHJvZHVjZSBfc21hbGwgbXVsdGlwbGVzXy4KCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KZ2dwbG90KGJhcmxleSkgKwogICAgZ2VvbV9wb2ludChhZXMoeCA9IHlpZWxkLCB5ID0gdmFyaWV0eSwgY29sb3IgPSB5ZWFyKSkgKwogICAgZmFjZXRfd3JhcCh+c2l0ZSkKYGBgCgpGb2N1c2luZyBvbiBzdW1tYXJpZXMgY2FuIGhlbHAuIF9CYXIgY2hhcnRzXyBhcmUgc29tZXRpbWVzIHVzZWQgZm9yCnN1bW1hcmllcywgYnV0IF9kb3QgcGxvdHNfIGFyZSB1c3VhbGx5IGEgYmV0dGVyIGNob2ljZS4KCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KYmFybGV5X3NpdGVfeWVhciA8LQogICAgZ3JvdXBfYnkoYmFybGV5LCBzaXRlLCB5ZWFyKSAlPiUKICAgIHN1bW1hcml6ZSh5aWVsZCA9IG1lYW4oeWllbGQpKSAlPiUKICAgIHVuZ3JvdXAoKQpwMSA8LSBnZ3Bsb3QoYmFybGV5X3NpdGVfeWVhcikgKwogICAgZ2VvbV9wb2ludChhZXMoeSA9IHNpdGUsIHggPSB5aWVsZCwgY29sb3IgPSB5ZWFyKSwgc2l6ZSA9IDMpCnAyIDwtIGdncGxvdChiYXJsZXlfc2l0ZV95ZWFyKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IHNpdGUsIHkgPSB5aWVsZCwgZmlsbCA9IHllYXIpLAogICAgICAgICAgICAgc2l6ZSA9IDMsCiAgICAgICAgICAgICBwb3NpdGlvbiA9ICJkb2RnZSIsIHdpZHRoID0gLjQpICsKICAgIGNvb3JkX2ZsaXAoKQpjb3dwbG90OjpwbG90X2dyaWQocDEsIHAyKQpgYGAKCkJlY2F1c2Ugb2YgdGhlIHdheSB3ZSBwZXJjZWl2ZSBiYXJzLCBpdCBpcyBpbXBvcnRhbnQgdG8gdXNlIGEgW3plcm8KYmFzZSBsaW5lIGZvciBiYXIKY2hhcnRzXShodHRwczovL2Zsb3dpbmdkYXRhLmNvbS8yMDE1LzA4LzMxL2Jhci1jaGFydC1iYXNlbGluZXMtc3RhcnQtYXQtemVyby8pLgoKIVtdKGltZy92aXozLTUyMHgyOTQuanBnKQoKIVtdKGltZy92aXo1LTUyMHgyODAuanBnKQoKCiMjIyBIYWlyIGFuZCBFeWUgQ29sb3IgRGF0YQoKQSBkYXRhIHNldCByZWNvcmRpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiBoYWlyIGFuZCBleWUgY29sb3IgYW5kIHNleCBpbgo1OTIgc3RhdGlzdGljcyBzdHVkZW50cy4KClRoZSBkYXRhIHNldCBpcyBhdmFpbGFibGUgYXMgYSBfY3Jvc3MtdGFidWxhdGlvbl87IGBhcy5kYXRhLmZyYW1lYApjb252ZXJ0cyBpdCB0byBhIGRhdGEgZnJhbWUuCgpgYGB7cn0KSGFpckV5ZURGIDwtIGFzLmRhdGEuZnJhbWUoSGFpckV5ZUNvbG9yKQpoZWFkKEhhaXJFeWVERikKYGBgCgpMb29raW5nIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgZXllIGNvbG9yOgoJCmBgYHtyfQpleWUgPC0KICAgIGdyb3VwX2J5KEhhaXJFeWVERiwgRXllKSAlPiUKICAgIHN1bW1hcml6ZShGcmVxID0gc3VtKEZyZXEpKSAlPiUKICAgIHVuZ3JvdXAoKQpnZ3Bsb3QoZXllKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IEV5ZSwgeSA9IEZyZXEpLCBwb3NpdGlvbiA9ICJkb2RnZSIpCmBgYAoKTWFwcGluZyBleWUgY29sb3IgdG8gY29sb3IgaW4gYWRkaXRpb24gdG8gdGhlIGhvcml6b250YWwgYXhpcyBjYW4gaGVscDoKCmBgYHtyfQpnZ3Bsb3QoZXllKSArIGdlb21fY29sKGFlcyh4ID0gRXllLCB5ID0gRnJlcSwgZmlsbCA9IEV5ZSksIHBvc2l0aW9uID0gImRvZGdlIikKYGBgCgpNb3JlIHNlbnNpYmxlIGNvbG9ycyB3b3VsZCBiZSBuaWNlIGJ1dCByZXF1aXJlcyBhIGJpdCBvZiB3b3JrOgoKYGBge3J9CmhhemVsX3JnYiA8LSBjb2wycmdiKCJicm93biIpICogMC43NSArIGNvbDJyZ2IoImdyZWVuIikgKiAwLjI1CmhhemVsIDwtIGRvLmNhbGwocmdiLCBhcy5saXN0KGhhemVsX3JnYiAvIDI1NSkpCgpjb2xzIDwtIGMoQmx1ZSA9IGNvbG9yc3BhY2U6OmxpZ2h0ZW4oY29sb3JzcGFjZTo6ZGVzYXR1cmF0ZSgiYmx1ZSIsIDAuMyksIDAuMyksCiAgICAgICAgICBHcmVlbiA9IGNvbG9yc3BhY2U6OmxpZ2h0ZW4oImZvcmVzdGdyZWVuIiwgMC4xKSwKICAgICAgICAgIEJyb3duID0gY29sb3JzcGFjZTo6bGlnaHRlbigiYnJvd24iLCAwLjAwMDEpLCAjIyAwLjM/CiAgICAgICAgICBIYXplbCA9IGNvbG9yc3BhY2U6OmxpZ2h0ZW4oaGF6ZWwsIDAuMykpCgpwYiA8LSBnZ3Bsb3QoZXllKSArCiAgICBnZW9tX2NvbChhZXMoeCA9IEV5ZSwgeSA9IEZyZXEsIGZpbGwgPSBFeWUpLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbHMpCnBiCmBgYAoKQSBfc3RhY2tlZCBiYXIgY2hhcnRfIGNhbiBhbHNvIGJlIHVzZWZ1bDoKCmBgYHtyfQpwc2IgPC0gZ2dwbG90KGV5ZSkgKwogICAgZ2VvbV9jb2woYWVzKHggPSAiIiwgeSA9IEZyZXEsIGZpbGwgPSBFeWUpLCBjb2xvciA9ICJsaWdodGdyZXkiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xzKQpwc2IKYGBgCgpBIF9waWUgY2hhcnRfIGNhbiBiZSBzZWVuIGFzIGEgc3RhY2tlZCBiYXIgY2hhcnQgaW4gcG9sYXIgY29vcmRpbmF0ZXM6CgpgYGB7cn0KKHBwIDwtIHBzYiArIGNvb3JkX3BvbGFyKCJ5IikpCmBgYAoKVGhlIGF4aXMgYW5kIGdyaWQgYXJlIG5vdCBoZWxwZnVsOyBhIF90aGVtZV8gYWRqdXN0bWVudCBjYW4gcmVtb3ZlIHRoZW06CgpgYGB7cn0KKHBwIDwtIHBwICsgdGhlbWVfdm9pZCgpKQpgYGAKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWluZm8iPiBUaGVtZXMgYXJlIGEgd2F5IHRvIGN1c3RvbWl6ZSB0aGUKbm9uLWRhdGEgY29tcG9uZW50cyBvZiBwbG90czogaS5lLiB0aXRsZXMsIGxhYmVscywgZm9udHMsIGJhY2tncm91bmQsCmdyaWRsaW5lcywgYW5kIGxlZ2VuZHMuIFRoZW1lcyBjYW4gYmUgdXNlZCB0byBnaXZlIHBsb3RzIGEgY29uc2lzdGVudApjdXN0b21pemVkIGxvb2suCgpUaGUgYGdndGhlbWVzYCBwYWNrYWdlIHByb3ZpZGVzIGEgbnVtYmVyIG9mIHRoZW1lcyB0byBlbXVsYXRlIHRoZQpzdHlsZSBvZiBkaWZmZXJlbnQgcHVibGljYXRpb25zLCBmb3IgZXhhbXBsZSBgdGhlbWVfd3NqYCBhbmQKYHRoZW1lX2Vjb25vbWlzdGAuICA8L2Rpdj4KCkhvdyB3ZWxsIGRvIGJhciBjaGFydHMgYW5kIHBpZSBjaGFydHMgd29yaz8KCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KY293cGxvdDo6cGxvdF9ncmlkKHBiLCBwcCkKYGBgCgpTb21lIHF1ZXN0aW9uczoKICAgICAKKiBXaGljaCBwbG90IG1ha2VzIGl0IGVhc2llciB0byB0ZWxsIHdoZXRoZXIgdGhlIHByb3BvcnRpb24gb2YKICBicm93bi1leWVkIHN0dWRlbnRzIGlzIGxhcmdlciBvciBzbWFsbGVyIHRoYXQgdGhlIHByb3BvcnRpb24gb2YKICBibHVlLWV5ZWQgc3R1ZGVudHMuCgoqIFdoaWNoIHBsb3QgbWFrZXMgaXQgZWFzaWVyIHRvIHRlbGwgd2hldGhlciB0aGVzZSBwcm9wb3J0aW9ucyBhcmUKICBsYXJnZXIgb3Igc21hbGxlciB0aGFuIDEvMiBvciAxLzQgb3IgMS8zPwoKTG9va2luZyBhdCB0aGUgcHJvcG9ydGlvbnMgd2l0aGluIGhhaXIgY29sb3IgYW5kIHNleDoKICAgICAgCmBgYHtyLCBmaWcud2lkdGggPSAxMH0KZXllX2hhaXJzZXggPC0KICAgIGdyb3VwX2J5KEhhaXJFeWVERiwgSGFpciwgU2V4KSAlPiUKICAgIG11dGF0ZShQcm9wID0gRnJlcSAvIHN1bShGcmVxKSkgJT4lCiAgICB1bmdyb3VwKCkKcDEgPC0gZ2dwbG90KGV5ZV9oYWlyc2V4KSArCiAgICBnZW9tX2NvbChhZXMoeCA9IEV5ZSwgeSA9IFByb3AsIGZpbGwgPSBFeWUpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xzKSArCiAgICBmYWNldF9ncmlkKEhhaXJ+U2V4KQpwMiA8LSBnZ3Bsb3QoZXllX2hhaXJzZXgpICsKICAgIGdlb21fY29sKGFlcyh4ID0gIiIsIHkgPSBQcm9wLCBmaWxsID0gRXllKSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29scykgKwogICAgY29vcmRfcG9sYXIoInkiKStmYWNldF9ncmlkKEhhaXJ+U2V4KSArCiAgICB0aGVtZV92b2lkKCkKY293cGxvdDo6cGxvdF9ncmlkKHAxLCBwMikKYGBgCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4gQSBtb3JlIGNvbXBsZXRlIGBnZ3Bsb3RgIHRlbXBsYXRlOgoKYGBgcgpnZ3Bsb3QoZGF0YSA9IDxEQVRBPikgKwogICAgPEdFT00+KG1hcHBpbmcgPSBhZXMoPE1BUFBJTkdTPiksCiAgICAgICAgICAgc3RhdCA9IDxTVEFUPiwKICAgICAgICAgICBwb3NpdGlvbiA9IDxQT1NJVElPTj4pICsKICAgIDwgLi4uIE1PUkUgR0VPTVMgLi4uID4gKwogICAgPENPT1JESU5BVEVfQURKVVNUTUVOVD4gKwogICAgPFNDQUxFX0FESlVTVE1FTlQ+ICsKICAgIDxGQUNFVElORz4gKwogICAgPFRIRU1FX0FESlVTVE1FTlQ+CmBgYAo8L2Rpdj4KCgojIyBQZXJjZXB0aW9uIGFuZCB0aGUgR3JhbW1hciBvZiBHcmFwaGljcwoKYGBge3J9CnJpdmVyIDwtIHNjYW4oImRhdGEvcml2ZXIuZGF0IikKcmQgPC0gZGF0YS5mcmFtZShmbG93ID0gcml2ZXIsIG1vbnRoID0gc2VxX2Fsb25nKHJpdmVyKSkKKHBwIDwtIGdncGxvdChyZCkgKyBnZW9tX3BvaW50KGFlcyh4ID0gbW9udGgsIHkgPSBmbG93KSkpCmBgYAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KKHBsIDwtIGdncGxvdChyZCkgKyBnZW9tX2xpbmUoYWVzKHggPSBtb250aCwgeSA9IGZsb3cpKSkKYGBgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwcCArIGNvb3JkX2ZpeGVkKDMuNSkKYGBgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpwbCArIGNvb3JkX2ZpeGVkKDMuNSkKYGBgCgojIyMgQSBTaW1wbGUgTW9kZWwgb2YgVmlzdWFsIFBlcmNlcHRpb24KClRoZSBleWVzIGFjcXVpcmUgYW4gaW1hZ2UsIHdoaWNoIGlzIHByb2Nlc3NlZCB0aHJvdWdoIHRocmVlIHN0YWdlcyBvZgptZW1vcnk6CgoqIEljb25pYyBtZW1vcnkKKiBXb3JraW5nIG1lbW9yeSwgb3Igc2hvcnQtdGVybSBtZW1vcnkKKiBMb25nLXRlcm0gbWVtb3J5CgpUaGUgZmlyc3QgcHJvY2Vzc2luZyBzdGFnZSBvZiBhbiBpbWFnZSBoYXBwZW5zIGluIGljb25pYyBtZW1vcnkuCgoqIEltYWdlcyByZW1haW4gaW4gaWNvbmljIG1lbW9yeSBmb3IgbGVzcyB0aGFuIGEgc2Vjb25kLgoqIFByb2Nlc3NpbmcgaW4gaWNvbmljIG1lbW9yeSBpcyBtYXNzaXZlbHkgcGFyYWxsZWwgYW5kIGF1dG9tYXRpYy4KKiBUaGlzIGlzIGNhbGxlZCBfcHJlYXR0ZW50aXZlIHByb2Nlc3NpbmdfLgoKUHJlYXR0ZW50aXZlIHByb2Nlc3NpbmcgaXMgYSBmYXN0IHJlY29nbml0aW9uIHByb2Nlc3MuCgpNZWFuaW5nZnVsIHZpc3VhbCBjaHVua3MgYXJlIG1vdmVkIGZyb20gaWNvbmljIG1lbW9yeSB0byBzaG9ydCB0ZXJtIG1lbW9yeS4KCiogVGhlc2UgY2h1bmtzIGFyZSB1c2VkIGJ5IGNvbnNjaW91cywgb3IgYXR0ZW50aXZlLCBwcm9jZXNzaW5nLgoqIEF0dGVudGl2ZSBwcm9jZXNzaW5nIG9mdGVuIGludm9sdmVzIGNvbnNjaW91cyBjb21wYXJpc29ucyBvciBzZWFyY2guCiogU2hvcnQgdGVybSBtZW1vcnkgaXMgbGltaXRlZDsKICAgICogaW5mb3JtYXRpb24gaXMgcmV0YWluZWQgZm9yIG9ubHkgYSBmZXcgc2Vjb25kczsKICAgICogb25seSB0aHJlZSBvciBmb3VycyBjaHVua3MgY2FuIGJlIGhlbGQgYXQgYSB0aW1lLgoKTG9uZyB0ZXJtIHZpc3VhbCBtZW1vcnkgaXMgYnVpbHQgdXAgb3ZlciBhIGxpZmV0aW1lLCB0aG91Z2gKaW5mcmVxdWVudGx5IHVzZWQgdmlzdWFsIGNodW5rcyBtYXkgYmVjb21lIGxvc3QuCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4qKlZpc3VhbCBEZXNpZ24gSW1wbGljYXRpb25zKioKCiogVHJ5IHRvIG1ha2UgYXMgbXVjaCB1c2Ugb2YgcHJlYXR0ZW50aXZlIGZlYXR1cmVzIGFzIHBvc3NpYmxlLgoKKiBSZWNvZ25pemUgd2hlbiBwcmVhdHRlbnRpdmUgZmVhdHVyZXMgbWlnaHQgbWlzbGVhZC4KCiogRm9yIGZlYXR1cmVzIHRoYXQgcmVxdWlyZSBhdHRlbnRpdmUgcHJvY2Vzc2luZyBrZWVwIGluIG1pbmQgdGhhdAogIHdvcmtpbmcgbWVtb3J5IGlzIGxpbWl0ZWQuCjwvZGl2PgoKIyMjIFNvbWUgVGVybXMgZm9yIERlc2NyaWJpbmcgVmlzdWFsaXphdGlvbnMKCi0gRGF0YSB0byBiZSB2aXN1YWxpemVkIGNvbnRhaW5zIF92YXJpYWJsZXNfIG9yIF9hdHRyaWJ1dGVzXyBtZWFzdXJlZAogIG9uIGluZGl2aWR1YWwgX2l0ZW1zXyBvciBfY2FzZXNfLgoKKiBfTGlua3NfIGFyZSByZWxhdGlvbnNoaXBzIHRoYXQgbWF5IGV4aXN0IGFtb25nIGl0ZW1zLCBlLmcuIG1vbnRocwogIHdpdGhpbiBhIHllYXIgb3IgY291bnRyaWVzIHdpdGhpbiBhIGNvbnRpbmVudC4KCiogX01hcmtzXyBhcmUgaW5kaXZpZHVhbCBnZW9tZXRyaWMgZW50aXRpZXMgdXNlZCB0byByZXByZXNlbnQgaXRlbXM6CiAgcG9pbnRzLiBiYXJzLCBldGMuCgoqIF9BZXN0aGV0aWNzXyBvciBfdmlzdWFsIGNoYW5uZWxzXyBhcmUgdGhlIHZpc3VhbCBmZWF0dXJlcyBvZiBtYXJrcwogIHRoYXQgY2FuIGJlIHVzZWQgdG8gZW5jb2RlIGF0dHJpYnV0ZXMuCgpUaGUgYGFlcyguLi4pYCBleHByZXNzaW9ucyBlc3RhYmxpc2ggdGhlIG1hcHBpbmcgYmV0d2VlbiBhdHRyaWJ1dGVzCmFuZCB2aXN1YWwgY2hhbm5lbHMuCgpUaGVzZSBpZGVhcyBjbG9zZWx5IG1pcnJvciB0aGUgc3RydWN0dXJlIG9mIHRoZSBfZ3JhbW1hciBvZiBncmFwaGljc18KYXMgaW1wbGVtZW50ZWQgaW4gYGdncGxvdGAuCgoKPiBNdW56bmVyLCBULiAoMjAxNCksIFtfVmlzdWFsaXphdGlvbiBBbmFseXNpcyBhbmQKPiAgRGVzaWduX10oaHR0cDovL3d3dy5jcy51YmMuY2EvfnRtbS92YWRib29rLyksIENSQyBQcmVzcy4KCj4gV2lsa2luc29uLCBMLiAoMjAwNSksIF9UaGUgR3JhbW1hciBvZiBHcmFwaGljc18sIDJuZCBlZCwgU3ByaW5nZXIuCgoKIyMjIENoYW5uZWxzIGFuZCB0aGVpciBBY2N1cmFjeQpBIHVzZWZ1bCBkaXN0aW5jdGlvbiBhbW9uZyBjaGFubmVsczoKCiogX01hZ25pdHVkZSBjaGFubmVsc18gY2FuIHJlZmxlY3Qgb3JkZXIgYW5kIG51bWVyaWMgdmFsdWVzLAogIGUuZy4gcG9zaXRpb24gb24gYW4gYXhpcywgbGVuZ3RoLCBhcmVhLCBicmlnaHRuZXNzLgoKKiBfSWRlbnRpdHkgY2hhbm5lbHNfIGNhbiBkaXN0aW5ndWlzaCBkaWZmZXJlbnQgdmFsdWVzIGJ1dCBub3QgcmVmbGVjdAogIG9yZGVyLCBlLmcuIGh1ZSwgc2hhcGUsIGdyb3VwaW5nLgoKU29tZSBjaGFubmVscyBhcmUgYmV0dGVyIGF0IGNvbnZleWluZyBpbmZvcm1hdGlvbiB0aGFuIG90aGVycy4KCk11bnpuZXIncyBvcmRlcmluZyBieSBhY2N1cmFjeToKCk1hZ25pdHVkZSBDaGFubmVscyAoT3JkZXJlZCwgTnVtZXJpY2FsKSAgICBJZGVudGl0eSBDaGFubmVscyAoQ2F0ZWdvcmljYWwpCi0tLS0tLS0tLS0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtLS0tLQpQb3NpdGlvbiBvbiBjb21tb24gc2NhbGUgICAgICAgICAgICAgICAgICAgU3BhdGlhbCBncm91cGluZwpQb3NpdGlvbiBvbiB1bmFsaWduZWQgc2NhbGUgICAgICAgICAgICAgICAgQ29sb3IgaHVlCkxlbmd0aCAoMUQgc2l6ZSkgICAgICAgICAgICAgICAgICAgICAgICAgICBTaGFwZQpUaWx0LCBhbmdsZQpBcmVhICgyRCBzaXplKQpEZXB0aCAoM0QgcG9zaXRpb24pCkNvbG9yIGx1bWluYW5jZSwgc2F0dXJhdGlvbgpDdXJ2YXR1cmUsIHZvbHVtZSAoM0Qgc2l6ZSkKCkxpbmUgd2lkdGggaXMgYW5vdGhlciBjaGFubmVsOyBub3Qgc3VyZSB0aGVyZSBpcyBhZ3JlZW1lbnQgb24gaXRzCmFjY3VyYWN5LCBidXQgaXQgaXMgbm90IGhpZ2guCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4qKlZpc3VhbCBEZXNpZ24gSW1wbGljYXRpb25zKioKClRyeSB0byBtYXAgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcyB0byB0aGUgc3Ryb25nZXN0IGNoYW5uZWxzLgo8L2Rpdj4KCgojIyMgQ29sb3IKCkNvbG9yIGlzIHZlcnkgZWZmZWN0aXZlIHdoZW4gdXNlZCB3ZWxsLgoKQnV0IHVzaW5nIGNvbG9yIHdlbGwgaXMgbm90IGVhc3kuCgpTb21lIG9mIHRoZSBpc3N1ZXM6CgoqIFBlcmNlcHRpb24gZGVwZW5kcyBvbiBjb250ZXh0LgoKKiBTaW1wbGUgY29sb3IgYXNzaWdubWVudHMgbWF5IG5vdCBzZXBhcmF0ZSBlcXVhbGx5IHdlbGwuCgoqIEVmZmVjdGl2ZW5lc3MgbWF5IHZhcnkgd2l0aCB0aGUgbWVkaXVtIChzY3JlZW4sIHByb2plY3RvciwgcHJpbnQpLgoKKiBTb21lIHBlb3BsZSBkbyBub3QgcGVyY2VpdmUgdGhlIGZ1bGwgc3BlY3R1cm0gb2YgY29sb3JzLgoKKiBHcmV5IHNjYWxlIHByaW50aW5nLgoKKiBTb21lIGNvbG9ycyBoYXZlIGN1bHR1cmFsIHNpZ25pZmljYW5jZS4KCiogQ3VsdHVyYWwgc2lnbmlmaWNhbmNlIG1heSB2YXJ5IGFtb25nIGN1bHR1cmVzIGFuZCB3aXRoIHRpbWUuCgpDb2xvciBwZXJjZXB0aW9uIGlzIHJlbGF0aXZlOgoKIVtdKGltZy9jaGVzczEucG5nKSAhW10oaW1nL2NoZXNzMi5wbmcpCgpBIG5vdGUgb24gW3JhaW5ib3cgY29sb3JzXSgKaHR0cHM6Ly9lZWVjb24udWliay5hYy5hdC9+emVpbGVpcy9uZXdzL2VuZHJhaW5ib3cvKS4KClNvbWUgdG9vbHMgZm9yIHNlbGVjdGluZyBwYWxldHRlcyBpbmNsdWRlOgoKKiBbQ29sb3JCcmV3ZXJdKGh0dHA6Ly9jb2xvcmJyZXdlcjIub3JnKTsgYXZhaWxhYmxlIGluIHRoZQogIGBSQ29sb3JCcmV3ZXJgIHBhY2thZ2UuCgoqIFtIQ0wgV2l6YXJkXShodHRwOi8vd3d3LmhjbHdpemFyZC5vcmcvKTsgYWxzbyBhdmFpbGFibGUgYXMgYGhjbHdpemFyZGAKICBpbiB0aGUgYGNvbG9yc3BhY2VgIHBhY2thZ2UuCgoKIyMgQSBHcmFtbWFyIG9mIERhdGEgTWFuaXB1bGF0aW9uCgpUaGUgYGRwbHlyYCBwYWNrYWdlIHByb3ZpZGVzIGEgbGFuZ3VhZ2UsIG9yIGdyYW1tYXIsIGZvciBkYXRhCm1hbmlwdWxhdGlvbi4KClRoZSBsYW5ndWFnZSBjb250YWlucyBhIG51bWJlciBvZiBfdmVyYnNfIHRoYXQgb3BlcmF0ZSBvbiB0YWJsZXMuCgpUaGUgbW9zdCBjb21tb25seSB1c2VkIHZlcmJzIG9wZXJhdGUgb24gYSBzaW5nbGUgZGF0YSBmcmFtZToKCiogYHNlbGVjdGA6IHBpY2sgdmFyaWFibGVzIGJ5IHRoZWlyIG5hbWVzCiogYGZpbHRlcmA6IGNob29zZSByb3dzIHRoYXQgc2F0aXNmeSBzb21lIGNyaXRlcmlhCiogYG11dGF0ZWA6IGNyZWF0ZSB0cmFuc2Zvcm1lZCBvciBkZXJpdmVkIHZhcmlhYmxlcwoqIGBhcnJhbmdlYDogcmVvcmRlciB0aGUgcm93cwoqIGBzdW1tYXJpemVgOiBjb2xsYXBzZSByb3dzIGRvd24gdG8gc3VtbWFyaWVzCgpUaGVyZSBhcmUgYWxzbyBhIG51bWJlciBvZiBgam9pbmAgdmVyYnMgdGhhdCBtZXJnZSBzZXZlcmFsIGRhdGEgZnJhbWVzCmludG8gb25lLgoKVGhlIGB0aWR5cmAgcGFja2FnZSBwcm92aWRlcyBhZGRpdGlvbmFsIHZlcmJzLCBzdWNoIGFzIGBwaXZvdF9sb25nZXJgCmFuZCBgcGl2b3Rfd2lkZXJgIGZvciByZXNoYXBpbmcgZGF0YSBmcmFtZXMuCgpUaGUgc2luZ2xlIHRhYmxlIHZlcmJzIGNhbiBhbHNvIGJlIHVzZWQgd2l0aCBgZ3JvdXBfYnlgIHRvIHdvcmsgYQpncm91cCBhdCBhIHRpbWUgaW5zdGVhZCBvZiBhcHBseWluZyB0byB0aGUgZW50aXJlIGRhdGEgZnJhbWUuCgpUaGUgZGVzaWduIG9mIGBkcGx5cmAgaXMgc3Ryb25nbHkgbW90aXZhdGVkIGJ5IFNRTC4KCgojIyBNb3JlIEV4YW1wbGVzCgpUaGVzZSBleGFtcGxlcyBzdGFydCB3aXRoIHJhdyBkYXRhIGFzIHlvdSBtaWdodCByZWNlaXZlIGl0IGZyb20gYQpyZXNlYXJjaGVyLCBhbmQgaW52b2x2ZSByZWFkaW5nIGFuZCBjbGVhbmluZyB0aGUgZGF0YS4KCgojIyMgV2luZCBUdXJiaW5lcyBpbiBJb3dhCgpUaGVyZSBhcmUgbWFueSB3aW5kIHR1cmJpbmVzIGluIElvd2EuICBEYXRhIGlzIGF2YWlsYWJsZSBmcm9tIHRoZQpbVS5TLiBXaW5kIFR1cmJpbmUgRGF0YWJhc2VdKGh0dHBzOi8vZWVyc2NtYXAudXNncy5nb3YvdXN3dGRiLykuICBBCnNuYXBzaG90IGlzIGF2YWlsYWJsZSBpcyBbaGVyZV0oZGF0YS91c193aW5kLmNzdikgYXMgYSBDU1YgZmlsZS4KCkNTViBmaWxlcyBhcmUgYSBjb21tb24gZm9ybSBvZiBkYXRhIGV4Y2hhbmdlLgoKKiBUaGV5IGFyZSBzaW1wbGUgdGV4dCBmaWxlcyB0aGF0IGFyZSBpbnRlbmRlZCB0byBiZSB3cml0dGVuIGFuZCByZWFkCiAgYnkgYSBjb21wdXRlci4KCiogU29tZSBDU1YgZmlsZXMgaW5jbHVkZSBhIGhlYWRlciBhbmQgYSBmb290ZXIgdGhhdCBuZWVkIHRvIGhlIGhhbmRsZWQuCgoqIE9uZSBpc3N1ZSBpcyB0aGF0IGEgY29tbWEgaXNuJ3QgYSBnb29kIHNlcGFyYXRvciBpbiBjb3VudHJpZXMgd2hlcmUKICBpdCBpcyB0aGUgZGVjaW1hbCBzZXBhcmF0b3IhCgoqIEEgQ1NWIGZpbGUgY2FuIGJlIHJlYWQgdXNpbmcgYHJlYWQuY3N2YCBvciBgcmVhZHI6OnJlYWRfY3N2YC4KClJlYWRpbmcgdGhlIHdpbmQgdHVyYmluZSBkYXRhOgoKYGBge3J9CndpbmRfdHVyYmluZXMgPC0gcmVhZC5jc3YoImRhdGEvdXNfd2luZC5jc3YiLCBjb21tZW50ID0gIiMiKQpgYGAKU29tZSBkYXRhIGNsZWFuaW5nIGlzIG5lZWRlZC4KCkZvY3VzIG9uIHRoZSB3aW5kIHR1cmJpbmVzIGluIElPV0EgKDE5IGlzIHRoZSBbRklQUyBjb3VudHkKY29kZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRklQU19jb3VudHlfY29kZSkgZm9yIElvd2EpOgoKYGBge3J9Cnd0X0lBIDwtIGZpbHRlcih3aW5kX3R1cmJpbmVzLCB0X2ZpcHMgJS8lIDEwMDAgPT0gMTkpCmBgYApEcm9wIGVudHJpZXMgd2l0aCBtaXNzaW5nIGxvbmdpdHVkZSBvciBsYXRpdHVkZSB2YWx1ZXM6CgpgYGB7cn0Kd3RfSUEgPC0gZmlsdGVyKHd0X0lBLCAhIGlzLm5hKHhsb25nKSwgISBpcy5uYSh5bGF0KSkKYGBgClNvbWUgbWlzc2luZyB5ZWFyIHZhbHVlcyBhcmUgZW5jb2RlZCBhcyAtOTk5OTsgcmVwbGFjZSB0aGVzZSB3aXRoIGBOQWA6CgpgYGB7cn0Kd3RfSUEgPC0gbXV0YXRlKHd0X0lBLCBwX3llYXIgPSByZXBsYWNlKHBfeWVhciwgcF95ZWFyIDwgMCwgTkEpKQpgYGAKVG8gc2hvdyB0aGUgbG9jYXRpb25zIG9mIHdpbmQgdHVyYmluZXMgb24gYSBtYXAsIGxvYWQgc29tZSBtYXAgZGF0YToKCmBgYHtyLCBldmFsID0gRkFMU0V9Cmlvd2Ffc2YgPC0gc2Y6OnN0X2FzX3NmKG1hcHM6Om1hcCgiY291bnR5IiwgImlvd2EiLCBwbG90ID0gRkFMU0UsIGZpbGwgPSBUUlVFKSkKYGBgClRvIHNob3cgdGhlIGxvY2F0aW9ucyBvZiB3aW5kIHR1cmJpbmVzIG9uIGEgbWFwLCBsb2FkIHNvbWUgbWFwIGRhdGE6CgpgYGB7ciwgZmlnLndpZHRoID0gOH0KaW93YV9zZiA8LQogICAgc2Y6OnN0X2FzX3NmKG1hcHM6Om1hcCgiY291bnR5IiwgImlvd2EiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBUUlVFKSkKcCA8LSBnZ3Bsb3QoKSArCiAgICBnZW9tX3NmKGRhdGEgPSBpb3dhX3NmKSArCiAgICBnZ3RoZW1lczo6dGhlbWVfbWFwKCkKcApgYGAKCkxvY2F0aW9ucyBmb3IgYWxsIHdpbmQgdHVyYmluZXMgaW4gaW93YToKCmBgYHtyLCBmaWcud2lkdGggPSA4fQpwICsgZ2VvbV9wb2ludChhZXMoeGxvbmcsIHlsYXQpLCBkYXRhID0gd3RfSUEpCmBgYAoKVXNpbmcgY29sb3IgdG8gc2hvdyB3aGVuIHRoZSB3aW5kIHR1cmJpbmVzIHdlcmUgIGJ1aWx0OgoKYGBge3IsIGZpZy53aWR0aCA9IDh9CnllYXJfYnJrIDwtYygwLCAyMDA1LCAyMDEwLCAyMDE1LCAyMDIwKQp5ZWFyX2xhYiA8LSBjKCJiZWZvcmUgMjAwNSIsCiAgICAgICAgICAgICAgIjIwMDUtMjAwOSIsCiAgICAgICAgICAgICAgIjIwMTAtMjAxNCIsCiAgICAgICAgICAgICAgIjIwMTUtMjAyMCIpCnd0X0lBIDwtCiAgICBtdXRhdGUod3RfSUEsCiAgICAgICAgICAgeWVhciA9IGN1dChwX3llYXIsCiAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSB5ZWFyX2JyaywKICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHllYXJfbGFiLAogICAgICAgICAgICAgICAgICAgICAgcmlnaHQgPSBGQUxTRSkpCnAgKyBnZW9tX3BvaW50KGFlcyh4bG9uZywKICAgICAgICAgICAgICAgICAgIHlsYXQsCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IHllYXIpLAogICAgICAgICAgICAgICBkYXRhID0gd3RfSUEsCiAgICAgICAgICAgICAgIHNpemUgPSAzKQpgYGAKCmBgYHtyIGV2YWwgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKcCA8LSBnZ3Bsb3QoKSArIGdlb21fc2YoZGF0YSA9IGlvd2Ffc2YpICsgZ2d0aGVtZXM6OnRoZW1lX21hcCgpCnAgKyBnZW9tX3BvaW50KGFlcyh4bG9uZywgeWxhdCksIGRhdGEgPSB3dF9JQSkKCnd0X0lBX3NmIDwtIHNmOjpzdF9hc19zZih3dF9JQSwgY29vcmRzID0gYygieGxvbmciLCAieWxhdCIpLCBjcnMgPSA0MzI2KQoKcCArIGdlb21fc2YoZGF0YSA9IGZpbHRlcih3dF9JQV9zZiwgeWVhciA8PSAyMDIwKSkKCmxpYnJhcnkoZ2dhbmltYXRlKQpwYSA8LSBwICsgZ2VvbV9zZihkYXRhID0gd3RfSUFfc2YpICsKICAgIHRyYW5zaXRpb25fbWFudWFsKHllYXIsIGN1bXVsYXRpdmUgPSBUUlVFKSArCiAgICBsYWJzKHRpdGxlID0gIldpbmQgdHVyYmluZXMgaW4gSW93YSIsCiAgICAgICAgIHN1YnRpdGxlID0gIlllYXIgPSB7Y3VycmVudF9mcmFtZX0iKQphbmltX3NhdmUoImZvby5naWYiLCBhbmltYXRlKHBhLCBmcHMgPSAxMCwgbmZyYW1lcyA9IDEwMCkpCmBgYAoKIyMjIENhbmNlciBNYXAKClRoZSB3ZWJzaXRlIDxodHRwOi8vd3d3LmNhbmNlci1yYXRlcy5pbmZvL2lhPiBwcm92aWRlcyBkYXRhIG9uCmNhbmNlciBpbmNpZGVuY2UgZm9yIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBjYW5jZXJzIGluIElvd2EuClRoZSBkYXRhIGZvciBsdW5nIGFuZCBicm9uY2h1cyBjYW5jZXIgaW4gMjAxMSBhcmUgYXZhaWxhYmxlCmluIGEgW2NzdiBmaWxlXShkYXRhL0ludmFzaXZlLUNhbmNlci1JbmNpZGVuY2UtUmF0ZXMtYnktQ291bnR5LWluLUlvd2EtTHVuZy1hbmQtQnJvbmNodXMtMjAxMS5jc3YpIGluIHRoZSBwcm9qZWN0LgoKV2UgY2FuIHJlYWQgdGhlIGZpbGUgd2l0aCBgcmVhZF9jc3ZgIGZyb20gdGhlIGByZWFkcmAgcGFja2FnZS4KCkxvb2tpbmcgYXQgdGhlIGZpbGUgc2hvd3Mgc29tZSB0aGluZ3MgdGhhdCBuZWVkIHRvIGJlIGNsZWFuZWQgdXA6CgoqIFR3byBoZWFkZXIgbGluZXMgYXQgdGhlIGJlZ2lubmluZwoqIFNvbWUgZm9vdGVyIGxpbmVzLgoqIFNvbWUgdmFsdWVzIGNvZGVzIGFzIGB+YC4KClRoZSBoZWFkZXIgY2FuIGJlIGhhbmRsZWQgYnkgdXNpbmcgYHNraXAgPSAyYCBpbiB0aGUgYHJlYWRfY3N2YCBjYWxsOgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KZm5hbWUgPC0gImRhdGEvSW52YXNpdmUtQ2FuY2VyLUluY2lkZW5jZS1SYXRlcy1ieS1Db3VudHktaW4tSW93YS1MdW5nLWFuZC1Ccm9uY2h1cy0yMDExLmNzdiIKZCA8LSByZWFkX2NzdihmbmFtZSwgc2tpcCA9IDIpCmhlYWQoZCkKYGBgCgpMZXQncyBmb2N1cyBvbiBhIGZldyB2YXJpYWJsZXMgYW5kIGdpdmUgdGhlbSBtb3JlIGNvbnZlbmllbnQgbmFtZXM6CgpgYGB7cn0KZCA8LSBzZWxlY3QoZCwgY291bnR5ID0gMSwgcG9wdWxhdGlvbiA9IDIsIGNvdW50ID0gMywgY3J1ZGVfcmF0ZSA9IDQpCmBgYAoKVGhlIGZvb3RlciBuZWVkcyB0byBiZSByZW1vdmVkOgoKYGBge3J9CnRhaWwoZCkKYGBgCgpPbmUgd2F5IHRvIHJlbW92ZSB0aGUgZm9vdGVyOgoKYGBge3J9CmQgPC0gZmlsdGVyKGQsICEgaXMubmEocG9wdWxhdGlvbikpCmQgPC0gZmlsdGVyKGQsIGNvdW50eSAhPSAiU1RBVEUiKQp0YWlsKGQpCmBgYAoKQ2hhbmdpbmcgYGNvdW50YCBhbmQgYGNydWRlX3JhdGVgIHRvIG51bWVyaWMgY2hhbmdlcyB0aGUgYH5gIGVudHJpZXMKdG8gbWlzc2luZyB2YWx1ZXMgKGBOQWApIHZhbHVlczoKCmBgYHtyfQpkIDwtIG11dGF0ZShkLCBjb3VudCA9IGFzLm51bWVyaWMoY291bnQpLCBjcnVkZV9yYXRlID0gYXMubnVtZXJpYyhjcnVkZV9yYXRlKSkKYGBgCgpJbiB0aGlzIGNhc2UgdGhlcmUgYXJlIG5vIHplcm8gY2FzZSB2YWx1ZXM7IHR3byB3YXlzIHRvIGNoZWNrOgoKYGBge3J9CmNvdW50KGQsIGNvdW50ID09IDApCmFueShkJGNvdW50ID09IDAsIG5hLnJtID0gVFJVRSkKYGBgCgpJdCBfbWlnaHRfIGJlIHJlYXNvbmFibGUgdG8gYXNzdW1lIHRoZXNlIHZhbHVlcyB3aGVyZSB6ZXJvLCBzbyByZXBsYWNlCnRoZW0gd2l0aCB6ZXJvczoKCmBgYHtyfQpkIDwtIHJlcGxhY2VfbmEoZCwgbGlzdChjb3VudCA9IDAsIGNydWRlX3JhdGUgPSAwKSkKYGBgCgpBIF9jaG9yb3BsZXRoIG1hcF8gdXNlcyBjb2xvciBvciBzaGFkaW5nIHRvIHJlcHJlc2VudCB2YWx1ZXMgbWVhc3VyZWQKZm9yIGRpZmZlcmVudCBnZW9ncmFwaGljIHJlZ2lvbnMuCgpXZSB3aWxsIG5lZWQgdG8gbWVyZ2UsIG9yIF9sZWZ0IGpvaW5fLCB0aGUgY2FuY2VyIGRhdGEgd2l0aCB0aGUgbWFwCmRhdGUgd2UgbG9hZGVkIGZvciB0aGUgd2luZCB0dXJiaW5lIG1hcC4KCkZvciBJb3dhIHRoaXMgY2FuIGJlIGRvbmUgd2l0aCB0aGUgY291bnR5IG5hbWUsIGJ1dCBzb21lIGNhcmUgaXMgbmVlZGVkLgoKYGBge3J9CmQkY291bnR5WzFdCmlvd2Ffc2YkSURbMV0KCmQgPC0gbXV0YXRlKGQsIGNuYW1lID0gY291bnR5LCBjb3VudHkgPSB0b2xvd2VyKGNvdW50eSkpCmlvd2Ffc2YgPC0gbXV0YXRlKGlvd2Ffc2YsIGNvdW50eSA9IHN1YigiaW93YSwiLCAiIiwgSUQpKQoKc2V0ZGlmZihkJGNvdW50eSwgaW93YV9zZiRjb3VudHkpCnNldGRpZmYoaW93YV9zZiRjb3VudHksIGQkY291bnR5KQoKZCA8LSBtdXRhdGUoZCwgY291bnR5ID0gc3ViKCInIiwgIiIsIGNvdW50eSkpCgpzZXRkaWZmKGQkY291bnR5LCBpb3dhX3NmJGNvdW50eSkKc2V0ZGlmZihpb3dhX3NmJGNvdW50eSwgZCRjb3VudHkpCmBgYAoKRGVmaW5lIGByYXRlMUtgIHZhcmlhYmxlIGFzIHRoZSBudW1iZXIgb2YgY2FzZXMgcGVyIDEwMDAgaW5oYWJpdGFudHMKYW5kIGxlZnQgam9pbiB0aGUgZGF0YSB0byB0aGUgcG9seWdvbnM6CgpgYGB7cn0KZCA8LSBtdXRhdGUoZCwgcmF0ZTFLID0gMTAwMCAqIChjb3VudCAvIHBvcHVsYXRpb24pKQptZCA8LSBsZWZ0X2pvaW4oaW93YV9zZiwgZCwgImNvdW50eSIpCmhlYWQobWQpCmBgYAoKQSBzaW1wbGUgbWFwOgoKYGBge3J9CmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkodmlyaWRpcykKZ2dwbG90KG1kKSArIGdlb21fc2YoYWVzKGZpbGwgPSByYXRlMUspKQpgYGAKCkFuIGltcHJvdmVkIHZlcnNpb246CgpgYGB7cn0KbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeSh2aXJpZGlzKQpnZ3Bsb3QobWQpICsKICAgIGdlb21fc2YoYWVzKGZpbGwgPSByYXRlMUspLAogICAgICAgICAgICBjb2xvciA9ICJncmV5IikgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWUgPSAiUmF0ZSBwZXIgMTAwMCIpICsKICAgIHRoZW1lX21hcCgpCmBgYAoKQSBzaW1wbGUgaW50ZXJhY3RpdmUgdmVyc2lvbiB1c2luZyBbYHBsb3RseWBdKGh0dHBzOi8vcGxvdC5seS9yLyk6CgpgYGB7cn0KbWRsIDwtIG11dGF0ZShtZCwKICAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlKGNuYW1lLCByb3VuZChyYXRlMUssIDEpLCBwb3B1bGF0aW9uLCBzZXAgPSAiXG4iKSkKcCA8LSBnZ3Bsb3QobWRsKSArCiAgICBnZW9tX3NmKGFlcyhmaWxsID0gcmF0ZTFLLAogICAgICAgICAgICAgICAgdGV4dCA9IGxhYmVsKSwgCiAgICAgICAgICAgIGNvbG9yID0gImdyZXkiKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMobmFtZSA9ICJSYXRlIHBlciAxMDAwIikgKwogICAgdGhlbWVfbWFwKCkKCnBsb3RseTo6Z2dwbG90bHkocCwgdG9vbHRpcCA9ICJ0ZXh0IikKYGBgClRoZSBbYGxlYWZsZXRgXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2xlYWZsZXQvKSBwYWNrYWdlIHN1cHBvcnRzCm1vcmUgc29waGlzdGljYXRlZCBpbnRlcmFjdGl2ZSBtYXBzOgoKPCEtLSBodHRwOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC9sZWdlbmRzLmh0bWwtLT4KYGBge3J9CmxpYnJhcnkobGVhZmxldCkKcGFsIDwtIGNvbG9yTnVtZXJpYygKICAgIHBhbGV0dGUgPSAidmlyaWRpcyIsCiAgICBkb21haW4gPSBtZCRyYXRlMUspCmxhYiA8LSBsYXBwbHkocGFzdGUwKHRvb2xzOjp0b1RpdGxlQ2FzZShtZCRjb3VudHkpLCAiPEJSPiIsCiAgICAgICAgICAgICAgICAgICAgICJSYXRlOiAiLCByb3VuZChtZCRyYXRlMUssIDEpLCAiPEJSPiIsCiAgICAgICAgICAgICAgICAgICAgICJQb3A6ICIsIHNjYWxlczo6Y29tbWEobWQkcG9wdWxhdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhY2N1cmFjeSA9IDEpKSwKICAgICAgICAgICAgICBodG1sdG9vbHM6OkhUTUwpCmxlYWZsZXQoc2Y6OnN0X3RyYW5zZm9ybShtZCwgNDMyNikpICU+JQogICAgYWRkUG9seWdvbnMod2VpZ2h0ID0gMiwKICAgICAgICAgICAgICAgIGNvbG9yID0gImdyZXkiLAogICAgICAgICAgICAgICAgZmlsbENvbG9yID0gfiBwYWwocmF0ZTFLKSwKICAgICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMSwKICAgICAgICAgICAgICAgIGhpZ2hsaWdodE9wdGlvbnMgPQogICAgICAgICAgICAgICAgICAgIGhpZ2hsaWdodE9wdGlvbnMoY29sb3IgPSAid2hpdGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyaW5nVG9Gcm9udCA9IFRSVUUpLAogICAgICAgICAgICAgICAgbGFiZWwgPSBsYWIpICU+JQogICAgYWRkTGVnZW5kKHBhbCA9IHBhbCwgdmFsdWVzID0gfiByYXRlMUspCmBgYAoKCiMjIyBVbmVtcGxveW1lbnQgTWFwCgpbTG9jYWwgQXJlYSBVbmVtcGxveW1lbnQgU3RhdGlzdGljcyBwYWdlXShodHRwczovL3d3dy5ibHMuZ292L2xhdS8pCmZyb20gdGhlIEJ1cmVhdSBvZiBMYWJvciBTdGF0aXN0aWNzIG1ha2VzIGF2YWlsYWJsZSBjb3VudHktbGV2ZWwKbW9udGhseSB1bmVtcGxveW1lbnQgZGF0YSBmb3IgYSAxNC1tb250aCB3aW5kb3cuIFRoZSBmaWxlIGZvciBGZWJydWFyeQoyMDIwIHRocm91Z2ggTWFyY2ggMjAyMSBpcyBhdmFpbGFibGUgaXMgYXZhaWxhYmxlIGF0CjxodHRwOi8vd3d3LnN0YXQudWlvd2EuZWR1L35sdWtlL2RhdGEvbGF1cy9sYXVjbnR5Y3VyMTQtMjAyMC50eHQ+IGFuZAppbiB0aGUgcHJvamVjdCBkYXRhIGZvbGRlci4KCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWluZm8iPiBUaGlzIGZpbGUgaXMgYSB0ZXh0IGZpbGUgYnV0IHVzZXMgYQpub24tc3RhbmRhcmQgc2VwYXJhdG9yLiBJdCBpcyBkZXNpZ25lZCBmb3IgaHVtYW4gcmVhZGFiaWxpdHkgYW5kIHVzZXMKYSBjb21tYSBhcyBhIF90aG91c2FuZHMgc2VwYXJhdG9yXyBvciBfZ3JvdXBpbmcgbWFya18uICBJdCBhbHNvCmluY2x1ZGVzIGhlYWRlciBhbmQgZm9vdGVyIGluZm9ybWF0aW9uLiBJdCBpcyBzdGlsbCByZWFzb25hYmx5IGVhc3kgdG8KcmVhZCBpbi4gIDwvZGl2PgoKT25lIHdheSB0byByZWFkIHRoZSBkYXRhIGludG8gUiBpczoKCmBgYHtyfQpsYXVzVVJMIDwtICJkYXRhL2xhdWNudHljdXIxNC0yMDIwLnR4dCIKbGF1c1VTIDwtIHJlYWQudGFibGUobGF1c1VSTCwKICAgICAgICAgICAgICAgICAgICAgY29sLm5hbWVzID0gYygiTEFVU0FyZWFDb2RlIiwgIlN0YXRlIiwgIkNvdW50eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlRpdGxlIiwgIlBlcmlvZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkxhYm9yRm9yY2UiLCAiRW1wbG95ZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJVbmVtcGxveWVkIiwgIlVuZW1wUmF0ZSIpLAogICAgICAgICAgICAgICAgICAgICBxdW90ZSA9ICciJywgc2VwID0gInwiLCBza2lwID0gNiwKICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFLCBzdHJpcC53aGl0ZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBUUlVFKQpmb290c3RhcnQgPC0gZ3JlcCgiLS0tLS0tIiwgbGF1c1VTJExBVVNBcmVhQ29kZSkKbGF1c1VTIDwtIGxhdXNVU1sxOihmb290c3RhcnQgLSAxKSxdCmBgYAoKSXQgbWF5IGJlIHVzZWZ1bCB0byBiZSBhYmxlIHRvIGFjY2VzcyB0aGUgY291bnR5IG5hbWUgYW5kIHN0YXRlIG5hbWUKc2VwYXJhdGVseToKCmBgYHtyfQpsYXVzVVMgPC0gc2VwYXJhdGUobGF1c1VTLCBUaXRsZSwgYygiY25hbWUiLCAic2NvZGUiKSwKICAgICAgICAgICAgICAgICAgIHNlcCA9ICIsICIsIGZpbGwgPSAicmlnaHQiKQpgYGAKClRoZSBgVW5lbXBSYXRlYCB2YXJpYWJsZSBpcyByZWFkIGFzIGNoYXJhY3RlciBkYXRhIGJlY2F1c2Ugb2YgbWlzc2luZwp2YWx1ZSBlbmNvZGluZywgc28gbmVlZHMgdG8gYmUgY29udmVydGVkIHRvIG51bWVyaWM6CgpgYGB7cn0KbGF1c1VTIDwtIG11dGF0ZShsYXVzVVMsIFVuZW1wUmF0ZSA9IGFzLm51bWVyaWMoVW5lbXBSYXRlKSkKYGBgCgpDaGVjayBmb3IgbWlzc2luZyB2YWx1ZXM6CgpgYGB7cn0Kc2VsZWN0X2lmKGxhdXNVUywgYW55TkEpICU+JSBuYW1lcygpCmBgYAoKVGhlIHN0YXRlIGNvZGUgaXMgbWlzc2luZyBmb3IgdGhlIERpc3RyaWN0IG9mIENvbHVtYmlhOgoKYGBge3J9CnNlbGVjdChsYXVzVVMsIGNuYW1lLCBzY29kZSkgJT4lCiAgICBmaWx0ZXIoaXMubmEoc2NvZGUpKSAlPiUKICAgIHVuaXF1ZSgpCmBgYAo8IS0tCk1pc3NpbmcgdmFsdWVzIGZvciBgVW5lbXBSYXRlYCBhcmUgYWxsIGZvciBQdWVydG8gUmljbyBhbmQgU2VwdGVtYmVyCjIwMTcuIEh1cnJpY2FuZSBNYXJpYSBtYWRlIGxhbmRmYWxsIG9uIFNlcHRlbWJlciAyMC4gLS0+CgpNYXJjaCBhbmQgQXByaWwgMjAyMCBudW1iZXJzIHdlcmUgbm90IGF2YWlsYWJsZSBmb3IgUHVlcnRvIFJpY286CgpgYGB7cn0Kc2VsZWN0KGxhdXNVUywgc2NvZGUsIFBlcmlvZCwgVW5lbXBSYXRlKSAlPiUKICAgIGZpbHRlcihpcy5uYShVbmVtcFJhdGUpKSAlPiUKICAgIHVuaXF1ZSgpCmBgYAoKVG8gY29tcHV0ZSB0aGUgbmF0aW9uYWwgbW9udGhseSB1bmVtcGxveW1lbnQgcmF0ZXMgb3ZlciB0aGlzIHBlcmlvZCB3ZQpuZWVkIHNvbWUgbW9yZSBkYXRhIGNsZWFuaW5nOgoKYGBge3J9CmxhdXNVUyA8LSBtdXRhdGUobGF1c1VTLAogICAgICAgICAgICAgICAgIFBlcmlvZCA9IGZjdF9pbm9yZGVyKFBlcmlvZCksCiAgICAgICAgICAgICAgICAgTGFib3JGb3JjZSA9IGFzLm51bWVyaWMoZ3N1YigiLCIsICIiLCBMYWJvckZvcmNlKSksCiAgICAgICAgICAgICAgICAgVW5lbXBsb3llZCA9IGFzLm51bWVyaWMoZ3N1YigiLCIsICIiLCBVbmVtcGxveWVkKSkpCmBgYAoKVW5lbXBsb3ltZW50IGR1cmluZyB0aGlzIHBlcmlvZCB3YXMgYWZmZWN0ZWQgc2lnbmlmaWNhbnRseSBieSB0aGUKQ09WSUQtMTkgcGFuZGVtaWMuICBBIHBsb3Qgc2hvd3MgYSBsYXJnZSBzcGlrZSBpbiBBcHJpbCAyMDIwOgoKYGBge3J9Cmdyb3VwX2J5KGxhdXNVUywgUGVyaW9kKSAlPiUKICAgIHN1bW1hcml6ZShVbmVtcGxveWVkID0gc3VtKFVuZW1wbG95ZWQsIG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgICAgTGFib3JGb3JjZSA9IHN1bShMYWJvckZvcmNlLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICAgIFVuZW1wUmF0ZSA9IDEwMCAqIChVbmVtcGxveWVkIC8gTGFib3JGb3JjZSkpICU+JQogICAgZ2dwbG90KGFlcyhQZXJpb2QsIFVuZW1wUmF0ZSwgZ3JvdXAgPSAxKSkgKwogICAgZ2VvbV9saW5lKCkKYGBgCgpBIGNob3JvcGxldGggbWFwIGNhbiBiZSB1c2VkIHRvIGxvb2sgYXQgaG93IHRoZSBpbXBhY3Qgd2FzIGRpc3RyaWJ1dGVkCmFjcm9zcyB0aGUgY291bnRyeS4KClRvIHNob3cgdW5lbXBsb3ltZW50IHJhdGVzIG9uIGEgbWFwIHdlIG5lZWQgdG8gbWVyZ2UgdGhlIHVuZW1wbG95bWVudCBkYXRhCndpdGggbWFwIGRhdGEuCgpUbyBtYXRjaCBjb3VudHkgdW5lbXBsb3ltZW50IGRhdGEgYW5kIGNvdW50eSBzaGFwZSBkYXRhIGl0IGlzIHNhZmVyIHRvCnVzZSB0aGUgbnVtZXJpYyBbRklQUyBjb3VudHkKY29kZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRklQU19jb3VudHlfY29kZSkuIFRoaXMgY2FuIGJlCmFkZGVkIHdpdGgKCmBgYHtyfQpsYXVzVVMgPC0gbXV0YXRlKGxhdXNVUywgZmlwcyA9IFN0YXRlICogMTAwMCArIENvdW50eSkKYGBgClNoYXBlIGRhdGEgZm9yIFVTIGNvdW50aWVzIGNhbiBiZSBvYnRhaW5lZCBmcm9tIGEgbnVtYmVyIG9mIHNvdXJjZXMgaW4gYQpudW1iZXIgb2YgZGlmZmVyZW50IGZvcm1hdHMuCgpIZXJlIGlzIG9uZSBhcHByb2FjaDoKCmBgYHtyfQpjb3VudGllc19zZiA8LSBzZjo6c3RfYXNfc2YobWFwczo6bWFwKCJjb3VudHkiLCBwbG90ID0gRkFMU0UsIGZpbGwgPSBUUlVFKSkKY291bnR5LmZpcHMgPC0KICAgIG11dGF0ZShtYXBzOjpjb3VudHkuZmlwcywgcG9seW5hbWUgPSBzdWIoIjouKiIsICIiLCBwb2x5bmFtZSkpICU+JQogICAgdW5pcXVlKCkKY291bnRpZXNfc2YgPC0gbGVmdF9qb2luKGNvdW50aWVzX3NmLCBjb3VudHkuZmlwcywgYygiSUQiID0gInBvbHluYW1lIikpCnN0YXRlc19zZiA8LSAgc2Y6OnN0X2FzX3NmKG1hcHM6Om1hcCgic3RhdGUiLCBwbG90ID0gRkFMU0UsIGZpbGwgPSBUUlVFKSkKYGBgClNvbWUgc3VtbWFyaWVzIG92ZXIgdGhlIHBlcmlvZCBjYW4gYmUgY29tcHV0ZWQgYXMKCmBgYHtyfQpzdW1tYXJ5VVMgPC0gZ3JvdXBfYnkobGF1c1VTLCBDb3VudHksIFN0YXRlLCBmaXBzKSAlPiUKICAgIHN1bW1hcml6ZShhdmdfdW5lbXAgPSBtZWFuKFVuZW1wUmF0ZSwgbmEucm0gPSBUUlVFKSwKICAgICAgICAgICAgICBtYXhfdW5lbXAgPSBtYXgoVW5lbXBSYXRlLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICAgIGFwcl91bmVtcCA9IFVuZW1wUmF0ZVtQZXJpb2QgPT0gIkFwci0yMCJdKSAlPiUKICAgIHVuZ3JvdXAoKQpoZWFkKHN1bW1hcnlVUykKYGBgCkEgY2hvcm9wbGV0aCBtYXAgb2YgdGhlIEFwcmlsIDIwMjAgdW5lbXBsb3ltZW50IHJhdGVzOgoKCmBgYHtyfQpsZWZ0X2pvaW4oY291bnRpZXNfc2YsIHN1bW1hcnlVUywgImZpcHMiKSAlPiUKICAgIGdncGxvdCgpICsKICAgIGdlb21fc2YoYWVzKGZpbGwgPSBhcHJfdW5lbXApKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMobmFtZSA9ICJSYXRlIiwgbmEudmFsdWUgPSAicmVkIikgKwogICAgdGhlbWVfbWFwKCkgKwogICAgZ2VvbV9zZihkYXRhID0gc3RhdGVzX3NmLCBjb2wgPSAiZ3JleSIsIGZpbGwgPSBOQSkKYGBgCgpVc2luZyBhIHZlcnkgdmlzaWJsZSBjb2xvciBmb3IgbWlzc2luZyBkYXRhIGlzIHVzZWZ1bCwgYXQgbGVhc3QgZHVyaW5nCmV4cGxvcmF0aW9uLgoKYGFudGlfam9pbmAgY2FuIHNob3cgdGhlIGNvdW50eSBnZW9tZXRyeSB0aGF0IGRvZXMgbm90IGhhdmUgYW4gZW50cnkKaW4gdGhlIHVuZW1wbG95bWVudCBkYXRhOgoKYGBge3J9CmFudGlfam9pbihjb3VudGllc19zZiwgc3VtbWFyeVVTLCAiZmlwcyIpCmBgYApTaGFubm9uIENvdW50eSwgU0QgKEZJUFMgNDYxMTMpLCB3YXMgcmVuYW1lZCB0byBbT2dsYWxhIExha290YSBDb3VudHldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL09nbGFsYV9MYWtvdGFfQ291bnR5LF9Tb3V0aF9EYWtvdGEpIGluIEp1bmUgMjAxNSBhbmQgZ2l2ZW4gYSBuZXcgRklQUyBjb2RlLCA0NjEwMi4KClRoZSBnZW9tZXRyeSBkYXRhIHRhYmxlIG5lZWRzIHRvIGJlIHVwZGF0ZWQ6CgpgYGB7cn0KY291bnRpZXNfc2YgPC0gbXV0YXRlKGNvdW50aWVzX3NmLCBmaXBzID0gcmVwbGFjZShmaXBzLCBmaXBzID09IDQ2MTEzLCA0NjEwMikpCmBgYAoKV2l0aCB0aGUgdXBkYXRlZCBkYXRhIHRoZSBtYXAgaXMgbm93IGNvbXBsZXRlOgoKYGBge3J9CmxlZnRfam9pbihjb3VudGllc19zZiwgc3VtbWFyeVVTLCAiZmlwcyIpICU+JQogICAgZ2dwbG90KCkgKwogICAgZ2VvbV9zZihhZXMoZmlsbCA9IGFwcl91bmVtcCkpICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lID0gIlJhdGUiLCBuYS52YWx1ZSA9ICJyZWQiKSArCiAgICB0aGVtZV9tYXAoKSArCiAgICBnZW9tX3NmKGRhdGEgPSBzdGF0ZXNfc2YsIGNvbCA9ICJncmV5IiwgZmlsbCA9IE5BKQpgYGAKCmBgYHtyLCBlY2hvID0gRkFMU0UsIGV2YWwgPSBGQUxTRX0KZ2dwb2x5MnNmIDwtIGZ1bmN0aW9uKHBvbHksIGNvb3JkcyA9IGMoImxvbmciLCAibGF0IiksCiAgICAgICAgICAgICAgICAgICAgICBpZCA9ICJncm91cCIsIHJlZ2lvbiA9ICJyZWdpb24iLCBjcnMgPSA0MzI2KSB7CiAgICBzZjo6c3RfYXNfc2YocG9seSwgY29vcmRzID0gY29vcmRzLCBjcnMgPSBjcnMpICU+JQogICAgZ3JvdXBfYnkoISEgYXMubmFtZShpZCksICEhIGFzLm5hbWUocmVnaW9uKSkgJT4lCiAgICBzdW1tYXJpemUoZG9fdW5pb24gPSBGQUxTRSkgJT4lCiAgICBzZjo6c3RfY2FzdCgiUE9MWUdPTiIpICU+JQogICAgdW5ncm91cCgpICU+JQogICAgZ3JvdXBfYnkoISEgYXMubmFtZShyZWdpb24pKSAlPiUKICAgIHN1bW1hcml6ZShkb191bmlvbiA9IEZBTFNFKSAlPiUKICAgIHVuZ3JvdXAoKQp9Cm1fc2YgPC0gZ2dwb2x5MnNmKHNvY3Zpejo6Y291bnR5X21hcCwgYygibG9uZyIsICJsYXQiKSwgImdyb3VwIiwgImlkIikKbV9zZiA8LSBtdXRhdGUobV9zZiwgZmlwcyA9IGFzLm51bWVyaWMoaWQpKQptX3NmIDwtIG11dGF0ZShtX3NmLCBmaXBzID0gcmVwbGFjZShmaXBzLCBmaXBzID09IDQ2MTEzLCA0NjEwMikpCmdncGxvdChtX3NmKSArIGdlb21fc2YoKQphdSA8LSBncm91cF9ieShsYXVzVVMsIGZpcHMpICU+JSBzdW1tYXJpemUoYXZnX3VyID0gbWVhbihVbmVtcFJhdGUsIG5hLnJtID0gVFJVRSkpCm11IDwtIGdyb3VwX2J5KGxhdXNVUywgZmlwcykgJT4lIHN1bW1hcml6ZShtYXhfdXIgPSBtYXgoVW5lbXBSYXRlLCBuYS5ybSA9IFRSVUUpKQpkYSA8LSBsZWZ0X2pvaW4obV9zZiwgYXUsICJmaXBzIikKZG0gPC0gbGVmdF9qb2luKG1fc2YsIG11LCAiZmlwcyIpCmdncGxvdChkYSwgYWVzKGZpbGwgPSBhdmdfdXIpKSArIGdlb21fc2Yoc2l6ZSA9IDAuMSkgKyBzY2FsZV9maWxsX3ZpcmlkaXMobmFtZSA9ICJSYXRlIiwgbmEudmFsdWUgPSAicmVkIikKZ2dwbG90KGRtLCBhZXMoZmlsbCA9IG1heF91cikpICsgZ2VvbV9zZihzaXplID0gMC4xKSArIHNjYWxlX2ZpbGxfdmlyaWRpcyhuYW1lID0gIlJhdGUiLCBuYS52YWx1ZSA9ICJyZWQiKQpnZ3Bsb3QobGVmdF9qb2luKG1fc2YsIGZpbHRlcihsYXVzVVMsIFBlcmlvZCA9PSAiQXByLTIwIiksICJmaXBzIiksIGFlcyhmaWxsID0gVW5lbXBSYXRlKSkgKyBnZW9tX3NmKHNpemUgPSAwLjEpICsgc2NhbGVfZmlsbF92aXJpZGlzKG5hbWUgPSAiUmF0ZSIsIG5hLnZhbHVlID0gInJlZCIpCmBgYAoKIyMjIEdhcG1pbmRlciBDaGlsZGhvb2QgTW9ydGFsaXR5IERhdGEKClRoZSBgZ2FwbWluZGVyYCBwYWNrYWdlIHByb3ZpZGVzIGEgc3Vic2V0IG9mIHRoZSBkYXRhIGZyb20gdGhlCltHYXBtaW5kZXJdKGh0dHA6Ly93d3cuZ2FwbWluZGVyLm9yZy8pIHdlYiBzaXRlLiBBZGRpdGlvbmFsIGRhdGEgc2V0cwphcmUgW2F2YWlsYWJsZV0oaHR0cDovL3d3dy5nYXBtaW5kZXIub3JnL2RhdGEvKS4KCiogQSBkYXRhIHNldCBvbiBjaGlsZGhvb2QgbW9ydGFsaXR5IGlzIGF2YWlsYWJsZSBsb2NhbGx5IGFzIGEgW2NzdgogIGZpbGVdKGh0dHA6Ly9ob21lcGFnZS5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL2dhcG1pbmRlci11bmRlcjVtb3J0YWxpdHkuY3N2KQogIG9yIGFuIFtFeGNlbAogIGZpbGVdKGh0dHA6Ly9ob21lcGFnZS5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL2dhcG1pbmRlci11bmRlcjVtb3J0YWxpdHkueGxzeCkuIFRoZQogIEV4Y2VsIGZpbGUgaXMgYWxzbyBhdmFpbGFibGUgaW4gdGhlIHByb2plY3QgZGF0YSBmb2xkZXIuCgoqIFRoZSBudW1iZXJzIHJlcHJlc2VudCBudW1iZXIgb2YgZGVhdGhzIHdpdGhpbiB0aGUgZmlyc3QgZml2ZSB5ZWFycwogIHBlciAxMDAwIGJpcnRocy4KCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LWluZm8iPiBNYW55IHJlc2VhcmNoZXJzIGxpa2UgdG8gbWFuYWdlIHRoZWlyCmRhdGEgaW4gYSBzcHJlYWRzaGVldC4gQmVpbmcgYWJsZSB0byByZWFkIHN1Y2ggYSBzaGVldCBkaXJlY3RseQpncmVhdGx5IGhlbHBzIGtlZXBpbmcgdGhlIHdvcmtmbG93IHJlcHJvZHVjaWJsZS4KCk1hbnkgc3ByZWFkc2hlZXRzIGNvbnRhaW4gaGVhZGVyLCBmb290ZXJzLCBhbmQgb3RoZXIgYW5ub3RhdGlvbnMgdG8KYWlkIGEgaHVtYW4gdmlld2VyLgoKQXMgbG9uZyBhcyB0aGUgZGF0YSBhcmUgaW4gYSByZWN0YW5ndWxhciByZWdpb24gaXQgaXMgdXN1YWxseSBub3QgaGFyZAp0byBleHRyYWN0IHRoZW0gcHJvZ3JhbW1hdGljYWxseS4gIDwvZGl2PgoKTG9hZGluZyB0aGUgZGF0YToKCmBgYHtyfQpsaWJyYXJ5KHJlYWR4bCkKZ2NtIDwtIHJlYWRfZXhjZWwoImRhdGEvZ2FwbWluZGVyLXVuZGVyNW1vcnRhbGl0eS54bHN4IikKbmFtZXMoZ2NtKVsxXQpuYW1lcyhnY20pWzFdIDwtICJjb3VudHJ5IgpoZWFkKGdjbSkKYGBgCgpUaGlzIGRhdGEgc2V0IGlzIGluIF93aWRlXyBmb3JtYXQuCgpBIF9sb25nXyB2ZXJzaW9uIGlzIHVzZWZ1bCBmb3Igd29ya2luZyB3aXRoIGBnZ3Bsb3RgLgoKYGBge3J9CnRnY20gPC0gcGl2b3RfbG9uZ2VyKGdjbSwgLTEsIG5hbWVzX3RvID0gInllYXIiLCB2YWx1ZXNfdG8gPSAidTVtb3J0IikKaGVhZCh0Z2NtKQp0Z2NtIDwtIG11dGF0ZSh0Z2NtLCB5ZWFyID0gYXMubnVtZXJpYyh5ZWFyKSkKaGVhZCh0Z2NtKQpgYGAKCgpTb21lIGV4cGxvcmF0aW9uczoKYGBge3J9CnAgPC0gZ2dwbG90KHRnY20pICsKICAgIGdlb21fbGluZShhZXMoeWVhciwgdTVtb3J0LCBncm91cCA9IGNvdW50cnkpLCBhbHBoYSA9IDAuMykKcApwbG90bHk6OmdncGxvdGx5KHApCmBgYAoKU29tZSBzZWxlY3RlZCBjb3VudHJpZXM6CgpgYGB7cn0KY291bnRyaWVzIDwtIGMoIlVuaXRlZCBTdGF0ZXMiLCAiVW5pdGVkIEtpbmdkb20iLCAiR2VybWFueSIsICJDaGluYSIsICJFZ3lwdCIpCmZpbHRlcih0Z2NtLCBjb3VudHJ5ICVpbiUgY291bnRyaWVzKSAlPiUKICAgIGdncGxvdCgpICsKICAgIGdlb21fbGluZShhZXMoeCA9IHllYXIsIHkgPSB1NW1vcnQsIGNvbG9yID0gY291bnRyeSkpCmBgYAoKRXhhbWluaW5nIHRoZSBtaXNzaW5nIHZhbHVlczoKCmBgYHtyfQp0Z2NtX21pc3MgPC0KICAgIGdyb3VwX2J5KHRnY20sIGNvdW50cnkpICU+JQogICAgc3VtbWFyaXplKGFueU5BID0gYW55KGlzLm5hKHU1bW9ydCkpKSAlPiUKICAgIGZpbHRlcihhbnlOQSkgJT4lCiAgICBwdWxsKGNvdW50cnkpCgpwIDwtIGdncGxvdChmaWx0ZXIodGdjbSwgY291bnRyeSAlaW4lIHRnY21fbWlzcykpICsKICAgIGdlb21fbGluZShhZXMoeCA9IHllYXIsIHkgPSB1NW1vcnQsIGdyb3VwID0gY291bnRyeSksIG5hLnJtID0gVFJVRSkKcApwbG90bHk6OmdncGxvdGx5KHApCmBgYAo=